unit utsOpenGLUtils; {$mode objfpc}{$H+} interface uses Classes, SysUtils, syncobjs, utsTextSuite, utsTypes; type TtsQuadPosF = array[0..3] of TtsPositionF; TtsCharRenderRefOpenGL = class(TtsCharRenderRef) public TextureID: Integer; // ID of OpenGL texture where the char is stored in TexCoordSize: TtsPositionF; // size of the char in texture coords (0.0 - 1.0) TexCoordPos: TtsPositionF; // position of the char in texture coords (0.0 - 1.0) VertexSize: TtsPositionF; // size of the char in world coords VertexPos: TtsPositionF; // size of the char in world coords constructor Create; end; PtsTextureUsageItem = ^TtsTextureUsageItem; TtsTextureUsageItem = packed record children: array[0..3] of PtsTextureUsageItem; end; PtsTextureTreeItem = ^TtsTextureTreeItem; TtsTextureTreeItem = packed record value: SmallInt; children: array[0..1] of PtsTextureTreeItem; ref: TtsCharRenderRefOpenGL; end; PtsFontTexture = ^TtsFontTexture; TtsFontTexture = packed record ID: Integer; // OpenGL texture ID Usage: PtsTextureTreeItem ; // tree of used texture space Next: PtsFontTexture; // next texture in list Prev: PtsFontTexture; // previouse texture in list Size: Integer; // size of this texture Count: Integer; // number of chars stored in this texture end; TtsBaseOpenGL = class(TtsRenderer) private fTextureSize: Integer; fColor: TtsColor4f; fRenderPos: TtsPosition; fFirstTexture: PtsFontTexture; fLastTexture: PtsFontTexture; procedure FreeTextures(var aTexture: PtsFontTexture); procedure FreeTextureTreeItem(var aItem: PtsTextureTreeItem); protected property Color: TtsColor4f read fColor; property RenderPos: TtsPosition read fRenderPos; procedure PushTexture(const aTexture: PtsFontTexture); function CreateNewTexture: PtsFontTexture; virtual; procedure FreeTexture(var aTexture: PtsFontTexture); virtual; procedure UploadTexData(const aCharRef: TtsCharRenderRefOpenGL; const aCharImage: TtsImage; const X, Y: Integer); virtual; protected function CreateRenderRef(const aChar: TtsChar; const aCharImage: TtsImage): TtsCharRenderRef; override; procedure FreeRenderRef(const aCharRef: TtsCharRenderRef); override; procedure BeginRender; override; procedure SetDrawPos(const X, Y: Integer); override; function GetDrawPos: TtsPosition; override; procedure MoveDrawPos(const X, Y: Integer); override; procedure SetColor(const aColor: TtsColor4f); override; public property TextureSize: Integer read fTextureSize write fTextureSize; constructor Create(const aContext: TtsContext; const aFormat: TtsFormat); destructor Destroy; override; end; EtsRendererOpenGL = class(EtsRenderer); implementation //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //TtsCharRenderRefOpenGL//////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// constructor TtsCharRenderRefOpenGL.Create; begin inherited Create; TextureID := 0; FillByte(TexCoordPos, SizeOf(TexCoordPos), 0); FillByte(TexCoordSize, SizeOf(TexCoordSize), 0); FillByte(VertexPos, SizeOf(VertexPos), 0); FillByte(VertexSize, SizeOf(VertexSize), 0); end; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //TtsBaseOpenGL///////////////////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// procedure TtsBaseOpenGL.FreeTextures(var aTexture: PtsFontTexture); begin if not Assigned(aTexture) then exit; FreeTextures(aTexture^.Next); FreeTexture(aTexture); end; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// procedure TtsBaseOpenGL.FreeTextureTreeItem(var aItem: PtsTextureTreeItem); begin if not Assigned(aItem) then exit; FreeTextureTreeItem(aItem^.children[0]); FreeTextureTreeItem(aItem^.children[1]); Dispose(aItem); aItem := nil; end; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// procedure TtsBaseOpenGL.PushTexture(const aTexture: PtsFontTexture); begin aTexture^.Prev := fLastTexture; if Assigned(fLastTexture) then fLastTexture^.Next := aTexture else fFirstTexture := aTexture; fLastTexture := aTexture; end; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// function TtsBaseOpenGL.CreateNewTexture: PtsFontTexture; begin result := nil; end; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// procedure TtsBaseOpenGL.FreeTexture(var aTexture: PtsFontTexture); begin if not Assigned(aTexture) then exit; FreeTextureTreeItem(aTexture^.Usage); if Assigned(aTexture^.Prev) then aTexture^.Prev^.Next := aTexture^.Next; if Assigned(aTexture^.Next) then aTexture^.Next^.Prev := aTexture^.Prev; Dispose(aTexture); aTexture := nil; end; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// procedure TtsBaseOpenGL.UploadTexData(const aCharRef: TtsCharRenderRefOpenGL; const aCharImage: TtsImage; const X, Y: Integer); begin // DUMMY end; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// function TtsBaseOpenGL.CreateRenderRef(const aChar: TtsChar; const aCharImage: TtsImage): TtsCharRenderRef; var GlyphWidth, GlyphHeight: Integer; function InsertToTree(const aItem: PtsTextureTreeItem; const X1, Y1, X2, Y2: SmallInt; out X, Y: Integer): PtsTextureTreeItem; var w, h: Integer; begin result := nil; w := X2 - X1; h := Y2 - Y1; if not Assigned(aItem) or Assigned(aItem^.ref) or (w < GlyphWidth) or (h < GlyphHeight) then exit; if (aItem^.value > 0) then begin result := InsertToTree(aItem^.children[0], X1, Y1, X2, aItem^.value, X, Y); if not Assigned(result) then result := InsertToTree(aItem^.children[1], X1, aItem^.value, X2, Y2, X, Y); end else if (aItem^.value < 0) then begin result := InsertToTree(aItem^.children[0], X1, Y1, -aItem^.value, Y2, X, Y); if not Assigned(result) then result := InsertToTree(aItem^.children[1], -aItem^.value, Y1, X2, Y2, X, Y); end else if (w = GlyphWidth) and (h = GlyphHeight) then begin X := X1; Y := Y1; result := aItem; end else begin new(aItem^.children[0]); new(aItem^.children[1]); FillByte(aItem^.children[0]^, SizeOf(aItem^.children[0]^), 0); FillByte(aItem^.children[1]^, SizeOf(aItem^.children[1]^), 0); if (w - GlyphWidth) < (h - GlyphHeight) then begin aItem^.value := Y1 + GlyphHeight; result := InsertToTree(aItem^.children[0], X1, Y1, X2, aItem^.value, X, Y); end else begin aItem^.value := -(X1 + GlyphWidth); result := InsertToTree(aItem^.children[0], X1, Y1, -aItem^.value, Y2, X, Y) end; end; end; function AddToTexture(const aTexture: PtsFontTexture): TtsCharRenderRefOpenGL; var x, y: Integer; item: PtsTextureTreeItem; begin item := InsertToTree(aTexture^.Usage, 0, 0, aTexture^.Size, aTexture^.Size, x, y); if not Assigned(item) then raise EtsRendererOpenGL.Create('unable to add glyph to texture'); item^.ref := TtsCharRenderRefOpenGL.Create; result := item^.ref; // Text Coords result.TextureID := aTexture^.ID; result.TexCoordPos.x := x / aTexture^.Size; result.TexCoordPos.y := y / aTexture^.Size; result.TexCoordSize.x := aCharImage.Width / aTexture^.Size; result.TexCoordSize.y := aCharImage.Height / aTexture^.Size; // Vertex Coords result.VertexPos.x := -aChar.GlyphRect.Left; result.VertexPos.y := -aChar.GlyphRect.Top - aChar.GlyphOrigin.y; result.VertexSize.x := aCharImage.Width; result.VertexSize.y := aCharImage.Height; UploadTexData(result, aCharImage, x, y); end; var tex: PtsFontTexture; begin result := nil; if aCharImage.IsEmpty then exit; GlyphWidth := aCharImage.Width + 1; GlyphHeight := aCharImage.Height + 1; // try to add to existing texture tex := fFirstTexture; while Assigned(tex) and not Assigned(result) do begin result := AddToTexture(tex); tex := tex^.Next; end; // create new texture if not Assigned(result) then begin if (aCharImage.Width > TextureSize) or (aCharImage.Height > TextureSize) then raise EtsRendererOpenGL.Create('char is to large to fit into a texture: ' + aChar.CharCode + ' (0x' + IntToHex(Ord(aChar.CharCode), 4) + ')'); tex := CreateNewTexture; result := AddToTexture(tex); end; if not Assigned(result) then raise EtsRendererOpenGL.Create('unable to creat render reference for char: ' + aChar.CharCode + ' (0x' + IntToHex(Ord(aChar.CharCode), 4) + ')'); end; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// procedure TtsBaseOpenGL.FreeRenderRef(const aCharRef: TtsCharRenderRef); var ref: TtsCharRenderRefOpenGL; tex: PtsFontTexture; function IsEmtpy(const aItem: PtsTextureTreeItem): Boolean; begin result := Assigned(aItem) and not Assigned(aItem^.children[0]) and not Assigned(aItem^.children[1]) and not Assigned(aItem^.ref); end; function RemoveFromTree(const aItem: PtsTextureTreeItem; const X1, Y1, X2, Y2: Integer): Boolean; var w, h: Integer; begin w := X2 - X1; h := Y2 - Y1; if not Assigned(aItem) or (w < ref.VertexSize.x) or (h < ref.VertexSize.y) then exit; result := (aItem^.ref = ref); if not result then begin if (aItem^.value > 0) then begin result := result or RemoveFromTree(aItem^.children[0], X1, Y1, X2, aItem^.value); result := result or RemoveFromTree(aItem^.children[1], X1, aItem^.value, X2, Y2); end else if (aItem^.value < 0) then begin result := result or RemoveFromTree(aItem^.children[0], X1, Y1, -aItem^.value, Y2); result := result or RemoveFromTree(aItem^.children[1], -aItem^.value, Y1, X2, Y2); end; end else aItem^.ref := nil; if result and IsEmtpy(aItem^.children[0]) and IsEmtpy(aItem^.children[1]) then begin FreeTextureTreeItem(aItem^.children[0]); FreeTextureTreeItem(aItem^.children[1]); FillByte(aItem^, SizeOf(aItem^), 0); end; end; begin try if not Assigned(aCharRef) or not (aCharRef is TtsCharRenderRefOpenGL) then exit; ref := (aCharRef as TtsCharRenderRefOpenGL); tex := fFirstTexture; while Assigned(tex) do begin if (tex^.ID = ref.TextureID) then begin if not RemoveFromTree(tex^.Usage, 0, 0, tex^.Size, tex^.Size) then raise EtsRendererOpenGL.Create('unable to remove render ref from texture'); if IsEmtpy(tex^.Usage) then begin if (tex = fFirstTexture) then fFirstTexture := nil; FreeTexture(tex); end; tex := nil; end else tex := tex^.Next; end; finally if Assigned(aCharRef) then aCharRef.Free; end; end; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// procedure TtsBaseOpenGL.BeginRender; begin inherited BeginRender; fRenderPos.x := 0; fRenderPos.y := 0; end; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// procedure TtsBaseOpenGL.SetDrawPos(const X, Y: Integer); begin fRenderPos.x := X; fRenderPos.y := Y; end; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// function TtsBaseOpenGL.GetDrawPos: TtsPosition; begin result := fRenderPos; end; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// procedure TtsBaseOpenGL.MoveDrawPos(const X, Y: Integer); begin fRenderPos.x := fRenderPos.x + X; fRenderPos.y := fRenderPos.y + Y; end; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// procedure TtsBaseOpenGL.SetColor(const aColor: TtsColor4f); begin fColor := aColor; end; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// constructor TtsBaseOpenGL.Create(const aContext: TtsContext; const aFormat: TtsFormat); begin inherited Create(aContext, aFormat); fFirstTexture := nil; fLastTexture := nil; fTextureSize := 2048; fColor := tsColor4f(1, 1, 1, 1); fRenderPos := tsPosition(0, 0); end; destructor TtsBaseOpenGL.Destroy; begin FreeTextures(fFirstTexture); inherited Destroy; end; end.