PDA

View Full Version : OpenGL GLSL - Text rendering query



AthenaOfDelphi
11-01-2018, 10:29 PM
Hi all,

So I'm finally getting back to writing some code and I'm working on what will ultimately be a reusable piece of code. The full details aren't important but what I want to do is as follows:-

Use a PNG image containing a character set (fixed width, fixed height, ASCII character codes Row 1 0-15, Row 2 16-31 etc.) - Alpha provides transparency information
Use another PNG image containing 16x16 blocks of colour (organised in the same index sequence as character set) - This is loaded and the colours extracted from the cells

From these, I want to render fixed size text in a 2D orthographic (I think that's the term) way. With the background being one colour and the text being another (if I choose to colorise it - I may not want to change the color of the font texture, but I can simply bind that to vertices so I'm not too worried about that).

I thought by having the font in white I could colorise it using glColor prior to my glBegin(GL_QUAD) glVertex..... etc. that binds the glyph in the font texture to the four vertices. Unfortunately this changes the color of the fill behind it, so as I understand it, I need to use shaders to achieve what I want but I'm struggling.

So this image shows my source images (font and palette):-

1501

And this, whilst basic shows what I'd like to do:-

1502

My current code is this (this renders on a 24x24 grid using 16x16 blocks so there is an 8 pixel gap between them):-



glClearColor(0,0,0,0); // background color of the context
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT); // clear background and depth buffer
glDisable(GL_BLEND);

glViewport(0,0,fRenderWidth,fRenderHeight);
glLoadIdentity; // set it to initial state
gluOrtho2D(0,fRenderWidth,fRenderHeight,0);

glEnable(GL_TEXTURE_2D);

glColor4f(1,1,1,1);

for y:=0 to 15 do //5 do
begin
ty:=y*24;
for x:=0 to 15 do //5 do
begin
tx:=x*24;

glDisable(GL_BLEND);
glBindTexture(GL_TEXTURE_2D,fPalettes[0]);

glBegin(GL_QUADS);
glTexCoord2f(x*ONE_16TH,y*ONE_16TH);
glVertex2i(tx,ty);
glTexCoord2f(x*ONE_16TH+ONE_16TH,y*ONE_16TH);
glVertex2i(tx+16,ty);
glTexCoord2f(x*ONE_16TH+ONE_16TH,y*ONE_16TH+ONE_16 TH);
glVertex2i(tx+16,ty+16);
glTexCoord2f(x*ONE_16TH,y*ONE_16TH+ONE_16TH);
glVertex2i(tx,ty+16);
glEnd;

glBindTexture(GL_TEXTURE_2D,fCharacterSets[0].texture);
glUseProgram(fTextRender);

glBegin(GL_QUADS);
glTexCoord2f(x*ONE_16TH,y*ONE_16TH);
glVertex2i(tx,ty);
glTexCoord2f(x*ONE_16TH+ONE_16TH,y*ONE_16TH);
glVertex2i(tx+16,ty);
glTexCoord2f(x*ONE_16TH+ONE_16TH,y*ONE_16TH+ONE_16 TH);
glVertex2i(tx+16,ty+16);
glTexCoord2f(x*ONE_16TH,y*ONE_16TH+ONE_16TH);
glVertex2i(tx,ty+16);
glEnd;

glUseProgram(0);

end;
end;

SilverWarior
12-01-2018, 03:54 AM
Nice to see you back in action :)

As for your problem. While I can't provide you any code since I don't have enough experience with OpenGL I can tell you that there are generally two approaches to achieve what you desire.

In first you simply draw your colored square on quad using your text texture as alpha mask so only text portions is actually rendered. As far as I know this doesn't require use of shaders. This should work just fine with your example images since your font image is just black and white.
In second approach you would use simple blender shader which uses alpha channel of your font texture to actually determine the render area. This shader would then render your font texture and blend it with desired color. And as far as I know when using shaders you don't need to provide your desired color from another texture but you could even pass it to shader as an input parameter.

AthenaOfDelphi
12-01-2018, 06:52 AM
Unfortunately if you use blending and issue say glColor4f(1.0,0.0,0.0,1.0); before creating the quad with the font texture on it, it alters the colour of the quad behind it. So I've been investigating using shaders. I think part of the problem is I'm not experienced enough with the OpenGL render pipeline and the associated background knowledge hence my looking for some assistance. I'm working on the 3D stuff and learning the OpenGL pipeline.

I should add, that the issue with blending may just be down to me not fully understanding the blending functions.

Thyandyr
12-01-2018, 09:47 AM
Good luck with this, I am interested in the progress since eventually I want my engine project to move away from VCL and have some sort of OpenGL based GUI.

Chebmaster
18-01-2018, 12:23 PM
what will ultimately be a reusable piece of code.
DON'T.
I beg you, stop and rethink your strategy.
Any code that has glBegin/glVertex/etc. in it will be doomed to the compatibility ghetto, limited to 20k vertices while any semi-modern hardware is capable of millions.
I will write article about Pascal beginners being cursed with this glBegin trap and why is it so horrible... Some day.
The first consequence is saying bye-bye to GLES and, correspondingly, to Raspberry Pi 2/3 support.

Here is my horrible code I replaced the glBegin travesty in my project with. It now runs on Raspberry Pi 2 and 3 (I use ANGLE over Direct3d on Windows to develop it because compiling on RPi takes forever):


TDumbUniMesh = class (TAbstractMesh)
//for absolutely dynamic things that get uploaded to the videocard
// every frame.
public
components: TRenderComponentSet;
colors: array of TVector4f;
normals: array of TVector3f;
texcoords: array of TVector2f;
vertices: array of TVector3f;
indices: array of GLushort; //word aka uint16
matrix: TMatrix4f;

constructor Create(_components: TRenderComponentSet =
[renc_Vertex, renc_Texcoord, renc_Color]);

//assumes origin in *upper* left corner (y axis downwards, z axis towards the viewer)
procedure AddQuad2d(left, top, right, bottom: GLfloat;
txleft: GLfloat = 0; txtop: GLfloat = 0; txright: GLfloat = 1; txbottom: GLfloat = 1);
procedure Render; override;
destructor Destroy;
procedure Color3f(_r, _g, _b: float);
procedure Color4f(_r, _g, _b, _a: float);
procedure Color(c: TVector3f); overload;
procedure Color(c: TVector4f); overload;
procedure LineTexCoords(txleft, txtop, txright, txbottom: float);
procedure AddLine(c: array of const; width: float); overload;
procedure AddLineLoop(c: array of const; width: float); overload;
procedure AddLine(points: array of TVector2f; width: float); overload;
procedure AddLineLoop(points: array of TVector2f; width: float); overload;

//specifies opposite corners of a rectangle
procedure SetLineTexCoords(a, b: TVector2f; _repeat: boolean); overload;

procedure SetLineGradientTo(c: TVector4f); // from current color to this
procedure SetLineTexCoords; // default glyph from | character in the font
protected
currentColor: TVector4f;
lineTC: array[0..3] of TVector2f;
lineTCrepeat: boolean;
lineGrad: TVector4f;
currentNormal: TVector3f;
maxVertex, maxIndex: integer;
function ParseAOCToVector2f(var c: array of const): TVector2fArray;
procedure GenerateLineMesh(points: array of TVector2f; width: float; loop: boolean);
procedure EnlargeBuffersIfNecessary; // according to maxVertex, maxIndex
public
property GetCurrentColor: TVector4f read currentColor;
end;


procedure TDumbUniMesh.Render;
var
i: integer;
myib: GLuint;
mymat: TMatrix4f;
using_shaders: boolean;
x: TRenderComponentEnum;
begin
if Length(indices) <= 0 then Exit;
if maxIndex < High(indices) then maxIndex:= High(indices);
using_shaders:= Mother^.GAPI.currentProgram.prog > 0;

case Mother^.GAPI.Mode of
{$ifndef glesonly}
gapi_GL21: begin
end;
{$endif glesonly}
gapi_GLES2: begin
if not using_shaders then Die(RuEn(
'Класс %0 не может рисовать, используя GLES2, когда в API матки не указана спецификация текущей программы.',
'Class %0 cannot render using GLES2 if no current program is specified in mother API.'),
[AnsiString(Self.ClassName)]);
end;
else
DieUnsupportedGLMode;
end;

if using_shaders then begin
with Mother^.GAPI.currentProgram do begin
for x in [renc_Matrix, renc_Vertex]
do if location[x] < 0
then Die(RuEn(
'Класс %0 не может рисовать, так как в спецификация текущей программы в API матки не хватает компоненты %1.',
'Class %0 cannot render because the program specified in the mother API doesn''t have component %1.'),
[AnsiString(Self.ClassName), GetEnumName(typeinfo(TRenderComponentEnum), ord(x))]);

for x in TRenderComponentSet do begin
if x = renc_Matrix then continue; // it then takes the matrix from the TGAPI singleton
if (location[x] >= 0) and not (x in components)
then Die(RuEn(
'Данный экземпляр %0 не может рисовать, так как текущая программа требует компоненты %1 которой у него нету.',
'This %0 instance cannot render because the current program requires the %1 component which it does not have.'),
[AnsiString(Self.ClassName), GetEnumName(typeinfo(TRenderComponentEnum), ord(x))]);
end;

CheckGLError;

glBindBuffer ( GL_ARRAY_BUFFER, 0 );
glBindBuffer ( GL_ELEMENT_ARRAY_BUFFER, 0 );
CheckGLError;

glEnableVertexAttribArray( location[renc_Vertex] );
CheckGLError;

glVertexAttribPointer( location[renc_Vertex], 3, GL_FLOAT, GL_FALSE, sizeof(vertices[0]), @vertices[0]);
CheckGLError;

if (renc_Texcoord in components) and (location[renc_Texcoord] >= 0) then begin
glEnableVertexAttribArray( location[renc_Texcoord] );
CheckGLError;

glVertexAttribPointer( location[renc_Texcoord], 2, GL_FLOAT, GL_FALSE, sizeof(texcoords[0]), @texcoords[0]);
CheckGLError;
end;

if (renc_Color in components) and (location[renc_Color] >= 0) then begin
glEnableVertexAttribArray( location[renc_Color] );
CheckGLError;

glVertexAttribPointer( location[renc_Color], 4, GL_FLOAT, GL_FALSE, sizeof(colors[0]), @colors[0]);
CheckGLError;
end;

if (renc_Normal in components) and (location[renc_Normal] >= 0) then begin
glEnableVertexAttribArray( location[renc_Normal] );
CheckGLError;

glVertexAttribPointer( location[renc_Normal], 3, GL_FLOAT, GL_FALSE, sizeof(normals[0]), @normals[0]);
CheckGLError;
end;

if (renc_Matrix in components) then begin
mymat:= matrix * TGAPI.matrix;
glUniformMatrix4fv( location[renc_Matrix], 1, GL_FALSE, @mymat);
end
else
glUniformMatrix4fv( location[renc_Matrix], 1, GL_FALSE, @TGAPI.matrix);

CheckGLError;

// http://openglbook.com/chapter-3-index-buffer-objects-and-primitive-types.html


glDrawElements(GL_TRIANGLES, maxIndex + 1, GL_UNSIGNED_SHORT, @indices[0]);
CheckGLError;

glDisableVertexAttribArray(location[renc_Vertex]);

if (renc_Texcoord in components) and (location[renc_Texcoord] >= 0)
then glDisableVertexAttribArray(location[renc_Texcoord]);

if (renc_Color in components) and (location[renc_Color] >= 0)
then glDisableVertexAttribArray(location[renc_Color]);

if (renc_Normal in components) and (location[renc_Normal] >= 0)
then glDisableVertexAttribArray(location[renc_Normal]);
end
end
{$ifndef glesonly}
else begin
// FFP compatible
{
glBegin(GL_TRIANGLES);
for i:=0 to maxIndex do begin
glColor4fv(@colors[indices[i]]);
glNormal3fv(@normals[indices[i]]);
glTexCoord3fv(@texcoords[indices[i]]);
glVertex4fv(@vertices[indices[i]]);
end;
glEnd;
}
// https://www.opengl.org/wiki/Client-Side_Vertex_Arrays

//glGetError(); //***TODO this is a hack for an error state provoked somewhere else. Note to self: find and eliminate
CheckGLError;

glEnableClientState(GL_VERTEX_ARRAY);
CheckGLError;
glVertexPointer(3, GL_FLOAT, 0, @vertices[0]);
CheckGLError;

if (renc_Color in components) then begin
glEnableClientState(GL_COLOR_ARRAY);
CheckGLError;
glColorPointer(4, GL_FLOAT, 0, @colors[0]);
end
else
glDisableClientState(GL_COLOR_ARRAY);
CheckGLError;

if (renc_Texcoord in components) then begin
glClientActiveTexture(GL_TEXTURE0);
CheckGLError;
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
CheckGLError;
glTexCoordPointer(2, GL_FLOAT, 0, @texcoords[0]);
end
else
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
CheckGLError;

if (renc_Normal in components) then begin
glEnableClientState(GL_NORMAL_ARRAY);
CheckGLError;
glNormalPointer(GL_FLOAT, 0, @normals[0]);
end
else
glDisableClientState(GL_NORMAL_ARRAY);
CheckGLError;


if (renc_Matrix in components) then begin
glMatrixMode(GL_MODELVIEW);
glLoadMatrixf(@matrix);
CheckGLError;
end;


glDrawElements(GL_TRIANGLES, maxIndex + 1, GL_UNSIGNED_SHORT, @indices[0]);
CheckGLError;
end;
{$endif}
end;

destructor TDumbUniMesh.Destroy;
begin
SetLength(colors, 0);
// SetLength(normals, 0);
SetLength(texcoords, 0);
SetLength(vertices, 0);
SetLength(indices, 0);
inherited;
end;



constructor TDumbUniMesh.Create(_components: TRenderComponentSet);
begin
components:= _components;
if not (renc_Vertex in components) then Die(RuEn(
'Класс %0 не приспособлен к отсутствию компоненты renc_Vertex',
'Class %0 cannot work without the renc_Vertex component'),
[AnsiString(Self.ClassName)]);

if renc_Color in components then begin
currentColor[0]:= 1;
currentColor[1]:= 1;
currentColor[2]:= 1;
currentColor[3]:= 1;
end;
if renc_Normal in components then begin
currentNormal[0]:= 0;
currentNormal[1]:= 0;
currentNormal[2]:= 1;
end;
maxIndex:= -1;
maxVertex:= -1;
if renc_Matrix in components then begin
FillChar(matrix, sizeof(matrix), 0);
matrix[0,0]:= 1;
matrix[1,1]:= 1;
matrix[2,2]:= 1;
matrix[3,3]:= 1;
end;
end;

procedure TDumbUniMesh.Color3f(_r, _g, _b: float);
begin
if not (renc_Color in components) then Exit;
currentColor[0]:= _r;
currentColor[1]:= _g;
currentColor[2]:= _b;
currentColor[3]:= 1.0;
end;

procedure TDumbUniMesh.Color4f(_r, _g, _b, _a: float);
begin
if not (renc_Color in components) then Exit;
currentColor[0]:= _r;
currentColor[1]:= _g;
currentColor[2]:= _b;
currentColor[3]:= _a;
end;

procedure TDumbUniMesh.Color(c: TVector3f);
begin
PVector3f(@currentColor)^:= c;
currentColor[3]:= 1.0;
end;

procedure TDumbUniMesh.Color(c: TVector4f);
begin
currentColor:= c;
end;

procedure TDumbUniMesh.LineTexCoords(txleft, txtop, txright, txbottom: float);
begin
if not (renc_Texcoord in components) then Exit;
lineTC[0][0]:= txleft;
lineTC[0][1]:= txtop;
lineTC[1][0]:= txleft;
lineTC[1][1]:= txbottom;
lineTC[2][0]:= txright;
lineTC[2][1]:= txtop;
lineTC[3][0]:= txright;
lineTC[3][1]:= txbottom;
end;


procedure TDumbUniMesh.EnlargeBuffersIfNecessary;
var nv: integer;
begin
if Length(indices) <= maxIndex + 1
then
Setlength(indices, max(16, max(maxIndex, Length(indices) * 2)));

while Length(vertices) < maxVertex + 1 do begin
nv:= max(16, max ( maxVertex, Length(vertices) * 2 ));
if renc_Texcoord in components then SetLength(texcoords, nv);
if renc_Color in components then SetLength(colors, nv);
SetLength(vertices, nv);
if renc_Normal in components then Setlength(normals, nv);
end;
end;

//reminder: here "top" denotes smaller Y values as my GUI uses y axis downwards
procedure TDumbUniMesh.AddQuad2d(left, top, right, bottom: GLfloat;
txleft, txtop, txright, txbottom: GLfloat);
var
firstindex, firstvertex, newlength, i: GLint;
begin
firstindex:= maxIndex + 1; //because are initialized to 0, not -1
firstvertex:= maxVertex + 1;

inc (maxIndex, 6);
inc (maxVertex, 4);

EnlargeBuffersIfNecessary;

indices[firstindex + 0]:= firstvertex + 0;
indices[firstindex + 1]:= firstvertex + 1;
indices[firstindex + 2]:= firstvertex + 2;
indices[firstindex + 3]:= firstvertex + 2;
indices[firstindex + 4]:= firstvertex + 3;
indices[firstindex + 5]:= firstvertex + 0;

while Length(vertices) < maxVertex + 6 do begin
newlength:= max(16, Length(vertices) * 2 );
if renc_Texcoord in components then SetLength(texcoords, newlength);
if renc_Color in components then SetLength(colors, newlength);
SetLength(vertices, newlength);
if renc_Normal in components then Setlength(normals, newlength);
end;

if renc_Normal in components then
for i:= firstvertex to firstvertex + 3
do normals[i]:= currentNormal;

if renc_Color in components then
for i:= firstvertex to firstvertex + 3
do colors[i]:= currentColor;

if renc_Texcoord in components then begin
texcoords[firstvertex + 0][0]:= txleft;
texcoords[firstvertex + 0][1]:= txtop;
// texcoords[firstvertex + 0][2]:= 0;

texcoords[firstvertex + 1][0]:= txright;
texcoords[firstvertex + 1][1]:= txtop;
// texcoords[firstvertex + 1][2]:= 0;

texcoords[firstvertex + 2][0]:= txright;
texcoords[firstvertex + 2][1]:= txbottom;
// texcoords[firstvertex + 2][2]:= 0;

texcoords[firstvertex + 3][0]:= txleft;
texcoords[firstvertex + 3][1]:= txbottom;
// texcoords[firstvertex + 3][2]:= 0;
end;


//dat was awful idea, if funny for i:= firstvertex to firstvertex + 3 do vertices[i][3]:= 1.0;

vertices[firstvertex + 0][0]:= left;
vertices[firstvertex + 0][1]:= top;
vertices[firstvertex + 0][2]:= 0;

vertices[firstvertex + 1][0]:= right;
vertices[firstvertex + 1][1]:= top;
vertices[firstvertex + 1][2]:= 0;

vertices[firstvertex + 2][0]:= right;
vertices[firstvertex + 2][1]:= bottom;
vertices[firstvertex + 2][2]:= 0;

vertices[firstvertex + 3][0]:= left;
vertices[firstvertex + 3][1]:= bottom;
vertices[firstvertex + 3][2]:= 0;
end;

Chebmaster
18-01-2018, 12:24 PM
(too long; didn't fit)

//replacement for GL_LINE_STRIP used in GUI everywhere
procedure TDumbUniMesh.AddLine(c: array of const; width: float);
begin
GenerateLineMesh(ParseAOCToVector2f(c), width, false);
end;

procedure TDumbUniMesh.AddLineLoop(c: array of const; width: float);
begin
GenerateLineMesh(ParseAOCToVector2f(c), width, true);
end;

procedure TDumbUniMesh.AddLine(points: array of TVector2f; width: float);
begin
GenerateLineMesh(points, width, false);
end;

procedure TDumbUniMesh.AddLineLoop(points: array of TVector2f; width: float);
begin
GenerateLineMesh(points, width, true);
end;

function TDumbUniMesh.ParseAOCToVector2f(var c: array of const): TVector2fArray;
var
i, k, n: integer;
v: float;
begin
if (Length(c) mod 2) <> 0 then Die(MI_ERROR_PROGRAMMER_NO_BAKA, [
'Odd number of parameters passed to TDumbLineMesh.AddLine']);
SetLength(Result, Length(c) div 2);
n:= 0;
k:= 0;
for i:= 0 to High(c) do begin
case c[i].Vtype of
vtInteger: v:= c[i].VInteger;
vtExtended: v:= c[i].VExtended^;
else
Die(MI_ERROR_PROGRAMMER_NO_BAKA,
['Wrong parameter type passed to TDumbUniMesh.AddLine, Vtype='
+ IntToStr(c[i].Vtype)]);
end;
Result[n][k]:= v;
k:= 1 - k;
if k = 0 then Inc(n);
end;
end;


procedure TDumbUniMesh.SetLineTexCoords(a, b: TVector2f; _repeat: boolean); overload;
begin
lineTC[0]:= a;
lineTC[1]:= b - a;
lineTCrepeat:= _repeat;
end;

procedure TDumbUniMesh.SetLineTexCoords; // default glyph from | character in the font
var pv4: PVector4f;
begin
pv4:= @Mother^.Text.BuiltinFont[Mother^.Text.FixedFontQuality].GlyphTexCoords^[bifgglyph_Pipe];
lineTC[0][0]:= pv4^[0];
lineTC[0][1]:= pv4^[1];
lineTC[1][0]:= pv4^[2] - pv4^[0];
lineTC[1][1]:= pv4^[3] - pv4^[1];
lineTCrepeat:= true;
end;



procedure TDumbUniMesh.GenerateLineMesh(points: array of TVector2f;
width: float; loop: boolean);
var
vectors, //vector goes from i to point i + 1
rightnormals, conormals, // normal goes clockwise in my GUI
// coordinate system (X axis goes right, Y axis goes down from the top left
// screen corner, which is NOT how GL usually is set up)
v, //vertex buffer. Calculate in 2df before copying to real vertices field
tc //texcoords.
: array of TVector2f;
idx: array of integer; //index buffer. To be copied to the real indices.
connect, degenerate: array of boolean;//the segment i connects smoothly with segment i - 1
i, j, a, b, c, numConnections, startInd, startVert, curV, curI: integer;
anglecosine, veclen, confrac, ffrac: float;
begin


if Length(points) < 2 then Die(MI_ERROR_PROGRAMMER_NO_BAKA,
['Not enough points passed to TDumbUniMesh.GenerateLineMesh']);
if loop then j:= length(points) else j:= length(points) - 1;
SetLength(vectors, j);
SetLength(rightnormals, j);
SetLength(conormals, j);
SetLength(connect, j);
SetLength(degenerate, j);
for i:= 0 to length(points) - 2 do
vectors[i]:= points[i + 1] - points[i];
if loop then
vectors[j - 1]:= points[0] - points[High(points)];

for i:= 0 to High(vectors) do begin
rightnormals[i][0]:= - vectors[i][1]; // points clockwise 90 deg from the vector
rightnormals[i][1]:= vectors[i][0];
QuickNormalize(rightnormals[i]);
ffrac:= FastInverseSquareRoot(sqr(vectors[i][0]) + sqr(vectors[i][1]));
conormals[i]:= vectors[i] * ffrac;
degenerate[i]:= ffrac > (1 / width);
end;

numConnections:= 0;
if loop then a:= 0
else begin
a:= 1;
connect[0]:= false;
end;
for i:= a to j - 1 do begin
if i = 0 then b:= j - 1 else b:= i - 1;
//connect *only* if angle is less that 90 degrees !
//otherwise render as separate (overlapping) segments
anglecosine:= DotProduct(rightnormals[b], rightnormals[i]);
connect[i]:= (anglecosine >= 0) and not degenerate[i] and not degenerate[b];
if connect[i] then inc(numConnections);
end;

width*= 0.5;

//finally generate the mesh
startInd:= MaxIndex + 1;
curI:= 0;
SetLength(idx, (2 * 3 * j) + (4 * 3 * numConnections));
//addlog('i/i %0/%1 %2 %3 %4 ',[curI, length(idx), j, numConnections, connect[0]]);

startVert:= MaxVertex + 1;
curV:= 0;
SetLength(v, (4 * j) + (1 * numConnections));
if renc_Texcoord in components then begin
SetLength(tc, Length(v));
end;

for i:= 0 to j - 1 do begin
if i = (j - 1)
then a:= 0 //next point index
else a:= i + 1;

if connect[i] or connect[a]
then confrac:= 0.5 * ( 2 * width / QuickLength(vectors[i]));

if connect[i] then begin
//adding the 4 triangles of the connection

if i > 0
then c:= curV //vertices added by the previous segment
else c:= length(v); //vertices that *will* be added by the last segment
// It's a good thing our algorithm is 100% predictable, isn't it?

idx[curI + 0]:= (* CurV - 1;
Так вот где таилась погибель моя!
Мне смертию кость угрожала!
Из мёртвой главы Access Violation in ig4icd32.dll,
Шипя, между тем, выползало *) c - 1;
idx[curI + 1]:= c - 3;
idx[curI + 2]:= CurV + 0 ;

idx[curI + 3]:= CurV + 1;
idx[curI + 4]:= c - 2;
idx[curI + 5]:= c - 1;

idx[curI + 6]:= CurV + 0;
idx[curI + 7]:= CurV + 1;
idx[curI + 8]:= c - 1;

idx[curI + 9]:= c - 1;
idx[curI +10]:= c - 2;
idx[curI +11]:= c - 3;

inc(curI, 12);

v[curV + 0]:= points[i] + ((conormals[i] - rightnormals[i]) * width);
v[curV + 1]:= points[i] + ((conormals[i] + rightnormals[i]) * width);
if renc_Texcoord in components then begin
tc[curV + 0]:= lineTC[0] + lineTC[1] * ToVector2f(0, confrac);
tc[curV + 1]:= lineTC[0] + lineTC[1] * ToVector2f(1, confrac);
end;
end
else begin
v[curV + 0]:= points[i] - (rightnormals[i] * width);
v[curV + 1]:= points[i] + (rightnormals[i] * width);
if renc_Texcoord in components then begin
tc[curV + 0]:= lineTC[0];
tc[curV + 1]:= lineTC[0]+ lineTC[1] * ToVector2f(1, 0);
end;
end;

idx[curI + 0]:= curV + 1; //1st body triangle
idx[curI + 1]:= curV + 0;
idx[curI + 2]:= curV + 2;

idx[curI + 3]:= curV + 2; //2nd body triangle
idx[curI + 4]:= curV + 3;
idx[curI + 5]:= curV + 1;

inc(curI, 6);

if connect[a] then begin
v[curV + 2]:= points[a] - ((conormals[i] + rightnormals[i]) * width);
v[curV + 3]:= points[a] - ((conormals[i] - rightnormals[i]) * width);
if renc_Texcoord in components then begin
tc[curV + 2]:= lineTC[0] + lineTC[1] * ToVector2f(0, 1 - confrac);
tc[curV + 3]:= lineTC[0] + lineTC[1] * ToVector2f(1, 1 - confrac);
end;

//middle vertex
v[curV + 4]:= points[a] + (conormals[a] - conormals[i]) * width * 0.5;
if renc_Texcoord in components then begin
tc[curV + 4]:= lineTC[0] + lineTC[1] * ToVector2f(0.5, 1);
end;

inc(curV, 5);
end
else begin
v[curV + 2]:= points[a] - (rightnormals[i] * width);
v[curV + 3]:= points[a] + (rightnormals[i] * width);
if renc_Texcoord in components then begin
tc[curV + 2]:= lineTC[0] + lineTC[1] * ToVector2f(0, 1);
tc[curV + 3]:= lineTC[0] + lineTC[1] * ToVector2f(1, 1);
end;

inc(curV, 4);
end;
end;

inc (MaxIndex, Length(idx));
inc (MaxVertex, Length(v));
EnlargeBuffersIfNecessary;

for i:= 0 to High(idx) do begin
indices[startInd + i]:= startVert + idx[i];
end;

ffrac:= 1 / High(v);
for i:= 0 to High(v) do begin
vertices[startVert + i]:= v[i];
if renc_Texcoord in components
then texcoords[startVert + i]:= tc[i];
if renc_Normal in components
then normals[startVert + i]:= currentNormal;
if renc_Texcoord in components
then colors[startVert + i]:= currentColor + lineGrad * (i * ffrac);
end;
end;

procedure TDumbUniMesh.SetLineGradientTo(c: TVector4f);
begin
lineGrad:= c - currentColor;
end;

.. and usage (converted my old uber-crappy code to this less-crappy abstraction layer):

Mesh:= TDumbUniMesh.Create;
Mesh.Color4f(
f * Mother^.Display.FadeIn, f * Mother^.Display.FadeIn, f * Mother^.Display.FadeIn, 1);
Mesh.AddQuad2d(
0, 0, Mother^.Display.ClientRect.Width, Mother^.Display.ClientRect.Height,
0, 0, 1, 1);
TGAPI.SetGLStatesForGUI;
case Mother^.GAPI.Mode of
{$ifndef glesonly}
gapi_GL21 : begin
glDisable(GL_ALPHA_TEST);
end;
{$endif glesonly}
gapi_GLES2: begin
end;
else
DieUnsupportedGLMode;
end;

//f_current_bgtex
//f_current_bgw
//f_current_bgh

glDisable(GL_BLEND);
//glEnable(GL_TEXTURE_2D);
if Mother^.Display.Background.custom = 0
then glBindTexture(GL_TEXTURE_2D, f_bgtex[Mother^.Display.Background._default])
else glBindTexture(GL_TEXTURE_2D, Mother^.Display.Background.custom);
Mesh.Render;
Mesh.Free;
{
glColor4f(f * Mother^.Display.FadeIn, f * Mother^.Display.FadeIn, f * Mother^.Display.FadeIn, 1);
glBindTexture(GL_TEXTURE_2D, f_bgtex);
glBegin(GL_QUADS);
glTexCoord2f(0, 0);
glVertex2f(0, 0); //bottom left corner
glTexCoord2f(0, 1);
glVertex2f(0, Mother^.Display.WindowClientRect.Height);
glTexCoord2f(1, 1);
glVertex2f(Mother^.Display.WindowClientRect.Width, Mother^.Display.WindowClientRect.Height);
glTexCoord2f(1, 0);
glVertex2f(Mother^.Display.WindowClientRect.Width, 0);
glEnd;
glEnable(GL_ALPHA_TEST);
glEnable(GL_BLEND);
}

AthenaOfDelphi
18-01-2018, 02:56 PM
Whilst I appreciate the advice that I shouldn't use glBegin... unfortunately posting a class that apparently doesn't use it doesn't really help me as it's way to complicated for me right now. I have no idea what the other alternatives are so I don't have a clue where to start looking in that class.

glBegin is how I've done all my OpenGL stuff and it's fine for what I'm doing at the moment. I know much of what I'm doing is less than optimal, but that aside, any advice on the question regarding shaders?

Thyandyr
18-01-2018, 04:09 PM
DON'T.
I beg you, stop and rethink your strategy.


I think it's way better to encourage people in what they are doing than to offer alternatives because there is some other/better/advanced/optimal/fancy/cross-platfrom/new ideology/etc... way, unless they ask.

At least I myself end up feeling demotivated if there are too many alternate approaches, to know which one is best would require learning about all of them, and that is too much new stuff in one go.

Chebmaster
18-01-2018, 05:37 PM
so I don't have a clue where to start looking in that class.
procedure TDumbUniMesh.Render; gives the rough idea of which (still sub-optimal but much better) approach could be used.
Ideally, there should be a community library for beginners to use instead of the accursed glBegin -- I shall see if I could adapt my DumbUniMesh into a standalone library.



any advice on the question regarding shaders?
It would be hard it what is shown is your current level.
First, you want to pass *two* colors to the shader. This is easily done *but* is, as far as I know, incompatible with the ancient "glColorXX" way of passing the data to the GPU.

So in addition to learning how to create shaders and bind input parameters to them (not that hard, but full of details rarely explained satisfactorily), you still *have* to learn a more modern method of passing parameters to the video card (I may be missing something here -- my knowledge was gained through much trial and error).

I'd give my examples, but my code is heavily diffused into abstraction classes and exception wrappers.

For now, have you tried simply doing it in two passes? One untextured quad for the background, then another glColor() and another, now textured, quad for the character. Yes, it's double overdraw. But it would work.

Chebmaster
18-01-2018, 05:45 PM
P.S. And "untextured" coluld be a single texel stretched wide -- I adapted this strategy for my GUI to avoid unnecessary state switches.

P.P.S. Annoyingly "GL_QUADS" is deprecated and GLES doesn't have it. I went total :rageface: when I tried drawing a textured trapezoid and found that there is no good way except tesselating the tar out of it. What a bummer!
I had to recreate GL_LINE_STRIP on my own because it does not exist either. There are only triangles. Indexed ones.
On the plus side I can now use Google's "GL over Direct3d" wrapper -- ain't that neat?

AthenaOfDelphi
18-01-2018, 06:49 PM
So my code does do a two pass at present and providing I use glColor4f(1.0,1.0,1.0,1.0); aka Opaque white, the blend works and the alpha is taken into account from the font texture. And everything is great, but when I change the color to say yellow, the blend changes the colour of the background quad. This is why I was looking at shaders.

Fundamentally I've been able to write a fragment shader to set the colour to a fixed value, but where I'm coming unstuck is if I use a vertex shader to extract the color (which I understand to be an interpolated value, along with the position information), I'm in a position where it breaks the 2D orthographic projection and nothing I've tried has worked. And I've tried quite a few variations of shader.

Chebmaster
20-01-2018, 02:58 PM
You should not use blend, you should use alpha test.


glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_ALPHA_TEST);
glAlphaFunc(GL_GREATER, 0.01);

For shaders... I am beginner myself, but look at my FFP emulation for my GUI:
(please note that I calculate matrix by hand for GLES compatibility where glRotate() and the like do not exist: these are deprecated)


#version 120

uniform mat4 u_matrix;

attribute vec3 a_position;
attribute vec4 a_color;
attribute vec2 a_texCoord;

varying vec2 v_texCoord;
varying vec4 v_color;

void main()
{
gl_Position = u_matrix * vec4(a_position[0], a_position[1], a_position[2], 1.0);
v_texCoord = a_texCoord;
v_color = a_color;
}


#version 120

uniform sampler2D u_texture;

varying vec2 v_texCoord;
varying vec4 v_color;

void main()
{
vec4 mytexel = texture2D( u_texture, v_texCoord);
if (mytexel[3] < 0.05) {
discard;
} else {
gl_FragColor = v_color * mytexel;
}
}

LP
20-01-2018, 04:36 PM
I thought by having the font in white I could colorise it using glColor prior to my glBegin(GL_QUAD) glVertex..... etc. that binds the glyph in the font texture to the four vertices. Unfortunately this changes the color of the fill behind it, so as I understand it, I need to use shaders to achieve what I want but I'm struggling.

Try calling the following before your rendering loop:



glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);



DON'T.
I beg you, stop and rethink your strategy.
Any code that has glBegin/glVertex/etc. in it will be doomed to the compatibility ghetto, limited to 20k vertices while any semi-modern hardware is capable of millions.
This is a really bad advice. See this: Nvidia: Deprecation Myths (https://www.slideshare.net/Mark_Kilgard/opengl-32-and-more/37-Deprecation_Myths_ulliFeature_removal_will). Of course everything pre-GL3 is considered legacy now, but it doesn't mean you can't use it for learning. Newer APIs such as Metal/Vulkan/Direct3D 12 are notoriously difficult to get started, so if you want to get something working quickly, glBegin/glEnd is as good as any other option, and in regards in performance, it is likely faster than the source code you suggested (and I challenge you to prove different: do the benchmarks!)

I would suggest to use whatever technique you find easier to learn - it is better to have something working in the way you want, than not having anything at all.

Chebmaster
20-01-2018, 06:48 PM
This is a really bad advice. See this: Nvidia: Deprecation Myths.
You are wrong.
I was not talking about "below 3.3". I was talking about "certain techniques below 2".

OpenGL 2.1 is *ancient* now - but it's what you should use for learning.
OpenGL *below* 2, though... It's not just deprecated, it's not just "left to rot" -- you will have to relearn *everything* you learned because those legacy-squared techniques do not scale.

I performed benchmarking myself and I found that glBegin-style code begins to lag at about 20 000 vertices. That's how badly "left to rot" it is. That's how inefficiently it is emulated.

So, you learn this, you acquire really bad habits learning it you may even begin making libraries for future use... and then you will have to relearn 50%. You will have to scrap your libraries or salvage what you could of them them using really ugly hacks. You will have to fight your bad habits.

I've been there. I've done that. I speak from experience.
It cost me months of my free time.
I become sad when I see people walking unawares into the same trap.

P.S. The glBegin technique grew outdated in 20th century, between Quake 2 and Quake 3. If you look at Open Arena sources you will see just half a dozen places where glBegin is used and the vast majority of them are for rendering a single fullscreen quad.
Long before Crysis. Long before Oblivion. Carmack saw them unfit in 1999.
Nuff said.


and I challenge you to prove different: do the benchmarks!
I definitely will (I'm curious myself how much better my new class is) but it may take weeks: my game engine is currently down for rehauling.
I can only say that when my subdivided rotating sphere was still working, the difference between glBegin and the new class was difference between SwapBuffers taking most of the allotted frame time and taking a silver of it (intel HD3000)

LP
20-01-2018, 10:52 PM
You are wrong.
I was not talking about "below 3.3". I was talking about "certain techniques below 2".
Sure, let's pretend you didn't see the link I've posted, or let's pretend that Mark J. Kilgard, the principal engineer in Nvidia responsible of OpenGL driver development, is also wrong - you surely know better, don't you?


I performed benchmarking myself and I found that glBegin-style code begins to lag at about 20 000 vertices. That's how badly "left to rot" it is. That's how inefficiently it is emulated.
I would like to see these benchmarks. On most desktop hardware we've done testing happens actually the opposite - glBegin/glEnd is close to performance to glDrawArrays with system pointers (similar to DrawPrimitiveUP in D3D9). When using OpenGL 4.5 features, including DSA, glMapNamedBufferRange (or glNamedBufferSubData) with immutable storage and GL_MAP_INVALIDATE_BUFFER_BIT, supplying vertex data for streaming each frame, it is difficult to get close to performance of the "legacy features" - in fact, we could only achieve better performance by using multiple buffers of gradually increasing sizes and other things like circular buffers with GL_MAP_PERSISTENT_BIT and otherwise AZDO-like features.

So sure, this is very much legacy, but as long as it is supported and works, if you don't want to learn shaders or any third-party libraries, this is the quickiest way to render something in 3D. Pencil and paper are also very ancient and you can argue that with your new shiny iPad you never need to learn how to hand-write anymore, just until you drop the damn thing on a hard floor or your batteries run out...

AthenaOfDelphi
20-01-2018, 11:23 PM
And, in the end, I've just found out what I was doing wrong :)

So this code works :)



glClearColor(0,0,0,1.0);
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT);
glViewport(0,0,fRenderWidth,fRenderHeight);
glLoadIdentity;
gluOrtho2D(0,fRenderWidth,fRenderHeight,0);
glEnable(GL_TEXTURE_2D);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glBindTexture(GL_TEXTURE_2D,fCharacterSets[0].texture);

for y:=0 to 15 do //5 do
begin
ty:=y*24;
for x:=0 to 15 do //5 do
begin
tx:=x*24;

idx:=x+(Y*16);

glDisable(GL_BLEND);

glBegin(GL_QUADS);
glColor4f(fGLPaletteColors[0][idx,0],fGLPaletteColors[0][idx,1],fGLPaletteColors[0][idx,2],1.0);
glVertex2i(tx,ty);
glVertex2i(tx+16,ty);
glVertex2i(tx+16,ty+16);
glVertex2i(tx,ty+16);
glEnd;

// Render the font
glColor4f(fGLPaletteColors[0][255-idx,0],fGLPaletteColors[0][255-idx,1],fGLPaletteColors[0][255-idx,2],1.0);

glEnable(GL_BLEND);

glBegin(GL_QUADS);
glTexCoord2f(x*ONE_16TH,y*ONE_16TH);
glVertex2i(tx,ty);

glTexCoord2f(x*ONE_16TH+ONE_16TH,y*ONE_16TH);
glVertex2i(tx+16,ty);

glTexCoord2f(x*ONE_16TH+ONE_16TH,y*ONE_16TH+ONE_16 TH);
glVertex2i(tx+16,ty+16);

glTexCoord2f(x*ONE_16TH,y*ONE_16TH+ONE_16TH);
glVertex2i(tx,ty+16);
glEnd;

end;
end;


And this is the output....

1509

So what I was doing wrong, was binding the palette texture to the vertices of the background colour. Instead, this code simply uses glColor4f to set the colour, then creates a quad, specifies the points for the background. Turns on blending, changes the colour and creates a quad, specifying texture coordinates corresponding to the required character.

Interestingly though, I had a problem that was really odd involving the second column (from the left) and the bottom row, being darker. No matter what I tried I couldn't stop this and it was like this was something to do with one of the textures. Originally I was loading my textures and setting these:-



glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);


With those commented out, that problem went away.

And that, as they say is that... now I can crack on and write some bad code ;) to get something working.

AthenaOfDelphi
20-01-2018, 11:35 PM
Forgot to add... thanks for all the suggestions chaps :)

paul_nicholls
21-01-2018, 10:07 AM
I'm glad you got it working, and welcome back to coding :)

cheers,
Paul

Chebmaster
21-01-2018, 01:48 PM
And this is the output....
:D
I'm glad you got it working!
Try experimenting with glDisable(GL_BLEND) and see if it still works (unless your intention was blending the background with what was already rendered there).
I think my advice earlier was wrong and you only need the test, without blending.


you didn't see the link I've posted
Sorry. You posted a link to a page with one paragraph of title text and some embedded rectangle that doesn't work without JavaScript. Okay, I temporarily enabled JS for slideshare.net and slidesharecdn.com. And what I see?
http://chebmaster.com/_share/slideshare_tons_of_scripts.jpg
A veritable wall of sites that suddenly want to run their JavaScript in my browser.
How about NO? >:(
I alvays ignore such dumps of websites full of suspicious third party scripts.

Added this to my hosts file:

127.0.0.1 www.slideshare.net
127.0.0.1 slideshare.net
127.0.0.1 slidesharecdn.com
127.0.0.1 public.slidesharecdn.com
so that that dump is always blocked.

Besides, judging by the title that was something a nVidia dev said 9 years ago. Lots happened since then and I am more interested in the situation of today.


On most desktop hardware we've done testing happens actually the opposite - glBegin/glEnd is close to performance to glDrawArrays
That's a very interesting result and I can't wait until I'm done with my rehaul to do some more benchmarking.
There must be some interesting dependency on driver version, operating system, the way you initialize GL or even if your app is 32 or 64-bit.
Because I got what I got, both on Win7/HD3000/i5 and Win10/GTX460/Phenom II.

LP
21-01-2018, 03:16 PM
:D
Sorry. You posted a link to a page with one paragraph of title text and some embedded rectangle that doesn't work without JavaScript. Okay, I temporarily enabled JS for slideshare.net and slidesharecdn.com. And what I see?
A veritable wall of sites that suddenly want to run their JavaScript in my browser.
How about NO? >:(
I alvays ignore such dumps of websites full of suspicious third party scripts.
It's a web site that hosts a PowerPoint presentation made by aforementioned Nvidia person. I also use a JavaScript blocker in FireFox myself, but for occasional viewing open links in Chromium (Edge on Windows), which is something you can do for such exceptional cases. It's too bad you didn't care to check the presentation, especially for yourself as your attitude was a classical example of Dunning-Krueger effect: you have barely learned shader basics (as you've said yourself), so being a beginner you try to give advice whether to use a particular technology or not, while not being an expert on this topic; and you fear to see a presentation without enabling JavaScript, so can't actually learn something new. Please don't do that, such attitude hinders the real talent you may have.

Here's a quote from that Nvidia presentation:


NVIDIA values OpenGL API backward compatibility
- We don't take API functionality away from you
- We aren't going to foce you to re-write apps
Does deprecated functionality "stay fast"?
- Yes, of course - and stays fully tested
- Bottom-line: Old & new features run fast



Besides, judging by the title that was something a nVidia dev said 9 years ago. Lots happened since then and I am more interested in the situation of today.
"You have to know the past to understand the present." This Nvidia presentation talks about OpenGL 3.2, which is exact moment the whole deprecation thing happened. I obviously can't speak for graphics vendors, but I believe their commitment to continue supporting all OpenGL features in their graphics drivers is driven both by video games industry (there are a lot of old games that use legacy code; have you played Starcraft 1 recently? It still works and that's DirectDraw with direct primary surface access :)) and enterprise sector, where you may find large code bases dating back to 90s.

So it boils down to the same thing people said in Pascal vs C thread: use the tool that you find most appropriate for your current task.

AthenaOfDelphi
21-01-2018, 03:44 PM
Well whilst we're talking about OpenGL.... would someone like to suggest a reason why glGetUniformLocation doesn't appear to be initialised in dglOpenGL. As best as I can tell, when it's loading the address of the routine, it always gets NIL. I've tried using normal means of starting up (i.e. not specifying a library) and I've also tried to force NVOGL32.DLL. In both cases, it doesn't appear to know about glGetUniformLocation. Either that, or it is somehow getting initialised properly and it can't find the uniform, which is a 2D sampler.

Any thoughts?

Chebmaster
21-01-2018, 04:22 PM
have you played Starcraft 1 recently?
I have not, but Open Arena takes up to 60% of my gaming time.
It runs perfectly.


NVIDIA values OpenGL API backward compatibility
That they do, and I believe glBegin is there to stay forever, BUT with a nasty catch: these technologies are only kept good enough to keep old games running. There is no need to make them efficient.


It's a web site that hosts a PowerPoint presentation
Oh!
So it was working with only originating domain scripts enabled! I just was expecting a video.


On most desktop hardware we've done testing happens actually the opposite - glBegin/glEnd is close to performance to glDrawArrays
Were you measuring overall frame time or just the time of calling the functions?

You see, I found the experimental way that driver sort of stores commands you issue somewhere inside itself and the real work begins (usually) only after you call SwapBuffers (e.g. wglSwapBuffers, glxSwapBuffers, eglSwapBuffers). So to see what is really going on you have to measure how long the SwapBuffers call takes, with vSync disabled, of course.

Preferably, with Aero desktop composition disabled as any semi-transparent effect overlapping your window usually adds extra 8..9ms

I found that *vast majority* of my thread's time is usually spent inside that call, exceptions being FBO creation and GLSL program linking.
And it is where the cost of glBegin is paid.
This was true for all platforms I tried, including various Windows, Linux and wine.

My game engine has a built-in profiler and displays thread time charts along the fps counter. I watch them like a hawk and it draws SwapBuffers time in bright red.

Chebmaster
21-01-2018, 04:33 PM
, or it is somehow getting initialised properly and it can't find the uniform, which is a 2D sampler.
1. It was so NICE of them to declare GLcharARB = Char; PGLcharARB = ^GLcharARB; when Char could be UnicodeChar and OpenGL *only* understands 8-bit strings.

2. It's capricious. Try

glGetUniformLocation(<program>, PAnsiChar(RawByteString('my_uniform_name'#0)));

3. It can return -1 if your uniform was not used and the GLSL compiler eliminated it.

P.S. I always play it overly safe using wrappers like this one:

class function TGAPI.SafeGetUniformLocation(prog: GLuint; name: RawByteString): GLint;
var
error: UnicodeString;
begin
try
case Mother^.GAPI.Mode of
{$ifndef glesonly}
gapi_GL21,
{$endif glesonly}
gapi_GLES2: begin
Result:= glGetUniformLocation(prog, PAnsiChar(name + #0));
CheckGLError(true);
end;
else
DieUnsupportedGLMode;
end;
except
Die(RuEn(
'Не удалось получить расположение постоянной %1 для программы %0',
'Failed to get uniform %1 location for program %0'
), [prog, name]);
end;
end;

where


procedure CheckGlError(DieAnyway: boolean);
var ec: GLenum;
begin
{$if defined(cpuarm) and not defined(debug)}
//Raspberry Pi
//some shit of a driver spams console with "glGetError 0x500"
// thus bringing FPS to its knees
if not DieAnyway then Exit;
{$endif}

ec:= glGetError();

if ec <> 0 then
if DieAnyway or Mother^.Debug.StopOnOpenGlErrors
then Die(RuEn(
'Ошибка OpenGL, %0',
'OpenGL error, %0'
),
[GlErrorCodeToString(ec)])
else
if Mother^.Debug.Verbose then AddLog(RuEn(
'Ошибка OpenGL, %0',
'OpenGL error, %0'
),
[GlErrorCodeToString(ec)]);
end;

-- flying over paranoiac's nest :)

LP
21-01-2018, 05:33 PM
I have not, but Open Arena takes up to 60% of my gaming time.
That they do, and I believe glBegin is there to stay forever, BUT with a nasty catch: these technologies are only kept good enough to keep old games running. There is no need to make them efficient.

I would advocate it slightly different: yes, they (Nvidia, AMD, etc.) need to keep old API interface and for that, they are likely providing a fixed-function pipeline wrapper on top of programmable pipeline. However, since they know their own architecture very well, it is very likely they are using most optimal approach to implement such wrapper. In contrast, when you jump directly to programmable pipeline and try to build an engine of your own, it is much more difficult for you to achieve at least *the same* level of performance as the legacy fixed-function pipeline built as wrapper, because you have to optimize it to many architectures, vendors, drivers, OS versions, etc.

Granted, if you use programmable pipeline properly, you can do much more than you could do with FFP: in a new framework that we're in process of publishing, you can use Compute shaders to produce a 3D volume surface, which is then processed by Geometry/Tessellation shaders - the whole beautiful 3D scene is built and rendered without sending a single vertex to GPU! Nevertheless, it doesn't mean you can't start learning with FFP, it is just as good as any other solution when what you need is to render something simple in 2D or 3D using GPU.

Besides, you never know, maybe some day the whole OpenGL will be made as a wrapper on top of Vulkan API, who knows... :)



Were you measuring overall frame time or just the time of calling the functions?

Either use external profiling tools (Visual Studio has GPU profiler, also Nvidia/GPU also provide their own tools) or at least measure the average frame latency (not frame rate).



You see, I found the experimental way that driver sort of stores commands you issue somewhere inside itself and the real work begins (usually) only after you call SwapBuffers (e.g. wglSwapBuffers, glxSwapBuffers, eglSwapBuffers). So to see what is really going on you have to measure how long the SwapBuffers call takes, with vSync disabled, of course.
This is driver-dependent, so they are free to choose whatever approach is more efficient, but commonly you may expect that the work begins (GPU works in parallel) immediately when you issue a draw call. "Swap Buffers" has to wait for all GPU work to finish before swapping surfaces, which is why you feel it taking most of the time.

Chebmaster
22-01-2018, 07:56 AM
maybe some day the whole OpenGL will be made as a wrapper on top of Vulkan API,
I am already using Google's GL ES over Direct 3d wrapper :D


it is much more difficult for you to achieve at least *the same* level of performance as the legacy fixed-function pipeline
And sometimes flat out impossible.
I have some museum pieces of the GeForce line (7025, I think?) where even barebones GLSL is noticeably slower than FFP. That's the main one reason I haven't abandoned FFP completely: a situation may arise where my engine renders the scene in really low resolution, then stretches the result using FFP. I consider that better alternative to not running.


Either use external profiling tools [...] or at least measure the average frame latency
Ok, I really need to get to that benchmarking. Now curiosity is gnawing at me.


(not frame rate)
Yesss, measuring FPS is the primary noob marker.


"Swap Buffers" has to wait for all GPU work to finish before swapping surfaces, which is why you feel it taking most of the time.
That, yes, but this shows that function calls themselves are (more often that not) negligible in the grand scheme of things.

I suggest we return to this when I have my tool set working again.

phibermon
23-01-2018, 05:26 PM
glFlush and glFinish are the correct calls to make to handle the GL command buffer. The swap operation essentially calls these, schedules the swap on v-sync and then blocks until that's complete.

GL commands might be buffered across a network, or to a display server (think remote GL on X11) or in the GL implementation you're using (mesa, nvidia drivers etc)

To correctly handle a v-synced GL application you should be setting up your frame so you spend the minimum amount of time possible on the blocking swap call.

You can use GL timers to get the 'server' side timing, calculate the command buffer overhead by comparing that against your 'client' side GL calls followed by flush and finish and then you know that you can call swap and meet the v-sync 'deadline' with the minimum amount of time blocked in the swap call.

If you stream resources to graphics memory? such a setup is crucial for getting the maximum bandwidth without dropping frames.

As far as legacy GL is concerned, or to give it the correct name : "immediate mode"? the clue is in the name. GL3+ (in the context of performance) is all about batching - minimizing the API calls and efficiently aligning data boundaries.

It's not about fixed function or programmable hardware - it's about how the data is batched and sent to that hardware, regardless of the actual functionality.

Display lists in older GL versions were a herald of things to come - they allow you to bunch together all your commands and allow the GL implementation to store the entire display list in whatever native format it pipes to the actual hardware, effectively storing the commands 'server' side so they don't have to be sent over the pipe every time. How they are handled is vendor specific, some display list implementations are no faster than immediate calls (some old ATI + intel drivers long ago were known for it)

So yes - IMMEDIATE mode GL, that is, versions before 3.x which are referred to here as 'legacy' - will always be slower than the modern APIs - it's not about vendor implementation regardless of how optimal or not their code is.

Other than display lists and any driver specific optimizations the vendor may of made - there's no 'batching' in legacy GL. This is why we have VBOs, instancing - all server side optimizations - this is why Vulkan and newer APIs are capable of better performance in various situations.

The bottleneck is call overhead, 'instruction latency and bandwidth' - call it what you will. It's not what you move or what is done with it when it gets there - it's how you move it.

GL2 is moving a ton of dirt with a teaspoon. GL3+ is moving a ton of dirt with a pneumatic digger. (Vulkan is moving a ton of dirt with a bespoke digger that has been built right next to the pile of dirt)

If you're only moving a teaspoon of dirt? use a teaspoon, it's easier to use than a digger - but don't defend your teaspoon when you need to move a ton of dirt.

Or something like that. Something something teaspoon. I think we can all agree : teaspoon.

Chebmaster
24-01-2018, 12:37 PM
If you're only moving a teaspoon of dirt? use a teaspoon [...] Or something like that. Something something teaspoon.
Much better said that me. Thank you.
I'll add from myself that there is also GLES (an unruly, hard to tame beast) that does not have immediate mode functions but is required to support some platforms.

Me, I created a wrapper classfor small to medium loads. It hides actual implementation (may be glVertexAttribPointer() or may be older, more basic vertex arrays) and only use it to move my teaspoons.
Why? Because this way it lets you think of *all* your data as meshes. And contains much, much less API-related code to rehaul if you have to change your rendering mechanisms later.


glFinish()
I observed modern intel drivers flat out ignore this. But legacy drivers (say, Windows XP + Gf 5200 FX) *require* this called *after* SwapBuffers, otherwise you won'y get accurate measurement
(my engine uses this value for controlling adaptive detail, alongside the fps meter)


so you spend the minimum amount of time possible on the blocking swap call.

So, rendering, *then* sh-- stuff like backround uploading of higher LODs and only then SwapBuffers...? That's... such a good idea. Why am I doing it backwards? :(


You can use GL timers to get the 'server' side timing,
Me, I usually get "yer ghetto intel video doesn't know that trick" and stop bothering :p

paul_nicholls
24-01-2018, 09:20 PM
Me, I usually get "yer ghetto intel video doesn't know that trick" and stop bothering :p

hahah! That's gold :)

laggyluk
26-01-2018, 08:38 AM
On the topic, using shaders: if you are drawing text on background you don't need to use transparency, just lerp between font and background color based on font alpha.

I don't know anything about OpenGL 2.0 because I stared with 3.0 using tutorials in C++. You don't really need to know much about C++ to apply this to pascal as OpenGL API calls stay the same. I guess that's why no one really bothers to make tutorials in Pascal :(

Learned mainly from this one with zero prior OpenGL knowledge and managed to pull off deferred renderer with SSAO and stuff: http://ogldev.atspace.co.uk/

AthenaOfDelphi
26-01-2018, 04:09 PM
On the topic, using shaders: if you are drawing text on background you don't need to use transparency, just lerp between font and background color based on font alpha.

I don't know anything about OpenGL 2.0 because I stared with 3.0 using tutorials in C++. You don't really need to know much about C++ to apply this to pascal as OpenGL API calls stay the same. I guess that's why no one really bothers to make tutorials in Pascal :(

Learned mainly from this one with zero prior OpenGL knowledge and managed to pull off deferred renderer with SSAO and stuff: http://ogldev.atspace.co.uk/

Thanks for that, I'll take a look. For the time being, my approach is working, allowing me to focus on the more interesting elements of the game I'm trying to write :)

SilverWarior
27-01-2018, 04:54 PM
Hey AthenaOfDelphi are you perhaps panning in participating in Lazarus Game Contest 2018? Based on this thread I'm assuming you plan on having a retro look in your game. Right?

AthenaOfDelphi
27-01-2018, 05:00 PM
No, I'm not having a go at a competition... I did look at it, but if I recall correctly, you have to release sources... I'd like to make money from this endeavour, so it's closed source all the way for me right now :)

And yes, it is due to have a retro look... mainly text based UI for the entire game.

SilverWarior
27-01-2018, 05:05 PM
No, I'm not having a go at a competition... I did look at it, but if I recall correctly, you have to release sources... I'd like to make money from this endeavour, so it's closed source all the way for me right now :)

That is understandable. The same reason is dissuading me from entering the mentioned competition.


And yes, it is due to have a retro look... mainly text based UI for the entire game.

Care to share more info on your game idea?

AthenaOfDelphi
27-01-2018, 05:52 PM
I don't want to say too much, it may never even see the light of day, but it is serving as a vehicle to refresh my coding mojo :D

But, I've been playing quite a bit of Factorio, Gunpoint, SpaceChem, TIS-100, Shenzhen I/O, Opus Magnum, Silicon Zeroes, Hacknet, Uplink... all of them make me think... but whilst they are all fantastic games, they kind of lack depth. Hacknet for example... once you've hacked one system, aside from the incremental difficulty which can be largely mitigated by upgraded in-game software, there's not much more to do... hack, move to next story point, hack, move to next story point. Silicon Zeroes... same, great game, but it only has a limited number of components for you to use and there is no sandbox, so it's get task, build solution, get task, build solution, with each one being harder than the last and don't get me wrong... some of the tasks are pretty evil.

As an example, I'm trying to build a solution for a 2 pipeline CPU design that has two registers... accumulator and backup... so you've got to build into the design the capability of handling things like this:- LDAC 2 SAVE 10. Which might translate to load accumulator with 2 (will be executed by pipeline 1) and save accumulator to memory location 10 (will be executed by pipeline 2). The components you have are number (a constant number), add, sub, multiply, equality, read memory, write memory, latch, registers, input selector, output selector, instruction decoder and instruction selector. What's available depends on the puzzle and you may or may not have to worry about tick limits (i.e. complete the execution of the program within a given number of clock ticks).

But essentially it's the same thing over and over and as stated, there is not sandbox so you can't just play with everything to try and make stuff because typically the puzzles have a limited selection of components.

So, with all that in mind, I'm trying to write the kind of game I want to play. If all goes to plan the action will take place in a simulated computer system complete with a BIOS and operating system. So presently I'm building the classes I need to simulate the hardware. At the same time, I need to write a cross assembler for my PC so I can write the OS for my simulated system and then set about writing some basic tools for the machine. The text rendering is part of the simulated video controller. 256 colour palette, standard ASCII character set (have PC, Atari ST and Atari 8 bit character sets) and character attributes that can specify blinking, character set and palette to use.

That's about as much as you're going to get out of me for now :D

SilverWarior
27-01-2018, 07:53 PM
I see you have similar taste for games like me. I was also disappointed in some of them because they lack the proper depth.
For instance I really liked Uplink but was disappointed by the fact that later game becomes more difficult simply by lowering the time that you have to hack certain computer. So I come up to idea so that there would be different OS-es on different computers where each OS would require different hacking approach.
Another thing that disappoints me in most hacking games is that when you hack to some computer you are presented with just a few folders and files. That is nothing like any real life computer. So my idea is to create this virtual file-structure and its contents based on the computer OS and the programs that are installed on them. Which programs are installed on them should depend on the intended use of such computers. So for instance a home computer would have mostly games and some multimedia programs on them. Business computers would have more document writing or some special business programs on them and so on.
Also in my idea I intent to abandon the classic hacking style seen in most hacking games or movies where you sit in front of your computer all the time and you can do just about everything from there. Instead my plan is to require gamer to go and perhaps infiltrate some organization in order to get direct access to their computers because they simply have to strong protection to be hack-able.

This idea of mine is another reason why I started working on my own UI library because I need a UI library that would allow me to develop multiple different UI-s preferably with ease.
Any way for now my idea is more or less just an idea since I haven't managed to make any of the systems it requires yet. Maybe someday in the future it becomes a reality but before that happens I may put some of my other ideas into the reality first as they are less complex and thus easier to make.

phibermon
02-02-2018, 04:26 AM
I love the ideas Athena! there was that abandoned game by notch (https://en.wikipedia.org/wiki/0x10c) that was going to incorperate a virtual machine that ran actual bytecode and I've dreamed of related concepts as I get bogged down in writing a bloody engine that can do tons of things I don't personally want to do ;)

I'd like to offer that if you plan on some future, server/client like multiplayer?

There's things that will save you a lot of time in the future, if you think about the difficulties of synchronizing/validating the virtual machines, at this point in the design.
For example you could ensure that two virtual machines communicate over a virtual network and add an api for this virtual network into the virtual machine if you wanted internal networking.
or/in addition, you could abstract the interface between the VM and it's input/outputs eg the 'display' - this abstraction allowing for local computer, single player setups but also server/client remote rendering.
The way I understand it - the bandwidth of server/client synchronization (think anti-cheating, screen rendering) was a big sticking point for 0x10c and its scalability/performance.

Would deffo like to hear more as and when you choose to say :D

AthenaOfDelphi
03-02-2018, 10:23 AM
The difficulty I have with discussing it is that part of the allure I hope will be the fact that the machine has flaws in it (which also will make it difficult for me to really enjoy it because I won't have to go looking for them)... flaws that once discovered could provide attack vectors to exploit other players machines or other machines within the game world. There should also be some undocumented features that can be leveraged but not necessarily to provide an in-game advantage.

As for synchronisation... it's not going to be necessary to synchronise player computers with one another. If all goes according to plan there will be a virtual internet that hosts a mail system, manufacturer websites and bulletin board systems where you can get quests and such like. Some of these will be transient, in that they exist or are accessible for the duration of a quest. But I want one of the key elements to be exploring the hardware and what's possible with it. If a player wants to simply sit there and write games for the virtual machine... go for it :)

One thing I have to be certain about is that there will be no means of injecting software into the virtual world from a PC (other than via the editor I'm working on for building content). You want software that isn't available, you'll have to write it. And it won't be software like Hacknet where you have the SSH cracker and such like.

I've got a roughed out design for the CPU, video controller and MMU. One of the things I'm looking at right now is to make sure I include features to allow the OS of the machine to be pre-emptive multi-tasking, which in itself is quite a daunting prospect as I'm going to have to write the OS.... I did think about possibly just porting a Linux flavour to it but I'd have to write a C/C++ compiler for that and there's no way I'm going to do that anytime soon :)

Underlying tech is Delphi for all the grunt (so if I get it right and manage to separate the renderer from the OS sufficiently well, I should be able to port to MacOSX quite easily), Lua for control/quests etc. and various low level languages within the machine itself. Things like the virtual internet will probably just be one big Delphi service (possibly with a PHP plugin - although that raises security control questions since I'd effectively have to create my own flavour of PHP so it was only accessing the resources of the virtual server on which it was running - I could possibly build a system based on real virtual machines but that then limits what I can do with them unless I started introducing a deamon in the background to control them, which I'd then have to hide from things like PS etc.). It would also make recovering from problems trickier. This does mean that in game mode, it's going to be an on-line game, but Sandbox will run on-line or off-line. I'm thinking of having clear separation between play and sandbox... so software created in play mode will not be available in sandbox mode and vice versa, but on-line sandbox will probably have a bulletin board system of it's own for people to chat and exchange software etc.

It's a big undertaking and as I say, it may never come to fruition, but if I get the machine running with an operating system, editor and assembler, then I'll be chuffed to bits... the rest would be a nice bonus.

pitfiend
05-02-2018, 10:34 PM
...my own UI library because I need a UI library that would allow me to develop multiple different UI-s preferably with ease.

Wasn't it better to have a single UIlib with themes?

SilverWarior
07-02-2018, 05:51 PM
Wasn't it better to have a single UIlib with themes?

I was initially thinking of having just one UI with theme support but later on I realized that would not be enough in order to realize my idea completely.