From f4dd4b6d0fcdfa236212949133d63b7174585b44 Mon Sep 17 00:00:00 2001 From: DeaDDooMER Date: Sun, 24 Apr 2022 17:00:11 +0300 Subject: [PATCH] gl: add new opengl render (initial implementation) --- src/game/Doom2DF.lpr | 7 + src/game/g_map.pas | 18 - src/game/g_monsters.pas | 1 + src/game/opengl/r_map.pas | 20 +- src/game/renders/opengl/r_atlas.pas | 266 +++++++++++ src/game/renders/opengl/r_draw.pas | 211 +++++++++ src/game/renders/opengl/r_map.pas | 612 +++++++++++++++++++++++++ src/game/renders/opengl/r_render.pas | 300 ++++++++++++ src/game/renders/opengl/r_textures.pas | 574 +++++++++++++++++++++++ src/game/sdl2/g_system.pas | 51 ++- src/shared/a_modes.inc | 12 +- 11 files changed, 2041 insertions(+), 31 deletions(-) create mode 100644 src/game/renders/opengl/r_atlas.pas create mode 100644 src/game/renders/opengl/r_draw.pas create mode 100644 src/game/renders/opengl/r_map.pas create mode 100644 src/game/renders/opengl/r_render.pas create mode 100644 src/game/renders/opengl/r_textures.pas diff --git a/src/game/Doom2DF.lpr b/src/game/Doom2DF.lpr index ff748ec..6fdc309 100644 --- a/src/game/Doom2DF.lpr +++ b/src/game/Doom2DF.lpr @@ -195,6 +195,13 @@ uses {$ENDIF} {$ELSEIF DEFINED(USE_STUBRENDER)} r_render in 'renders/stub/r_render.pas', + {$ELSEIF DEFINED(USE_NEWGL)} + {$I ../shared/vampimg.inc} + r_render in 'renders/opengl/r_render.pas', + r_atlas in 'renders/opengl/r_atlas.pas', + r_textures in 'renders/opengl/r_textures.pas', + r_draw in 'renders/opengl/r_draw.pas', + r_map in 'renders/opengl/r_map.pas', {$ELSE} {$FATAL render driver not selected} {$ENDIF} diff --git a/src/game/g_map.pas b/src/game/g_map.pas index 5ce07e6..9a51d3b 100644 --- a/src/game/g_map.pas +++ b/src/game/g_map.pas @@ -196,15 +196,6 @@ const GridDrawableMask = (GridTagBack or GridTagStep or GridTagWall or GridTagDoor or GridTagAcid1 or GridTagAcid2 or GridTagWater or GridTagFore); - -type - TBinHeapPanelDrawCmp = class - public - class function less (const a, b: TPanel): Boolean; inline; - end; - - TBinHeapPanelDraw = specialize TBinaryHeapBase; - var gWalls: TPanelArray; gRenderBackgrounds: TPanelArray; @@ -225,7 +216,6 @@ var gdbg_map_use_accel_render: Boolean = true; gdbg_map_use_accel_coldet: Boolean = true; profMapCollision: TProfiler = nil; //WARNING: FOR DEBUGGING ONLY! - gDrawPanelList: TBinHeapPanelDraw = nil; // binary heap of all walls we have to render, populated by `g_Map_CollectDrawPanels()` gCurrentMap: TDynRecord = nil; gCurrentMapFileName: AnsiString = ''; // so we can skip texture reloading @@ -517,14 +507,6 @@ begin end; end; - -class function TBinHeapPanelDrawCmp.less (const a, b: TPanel): Boolean; inline; -begin - if (a.tag < b.tag) then begin result := true; exit; end; - if (a.tag > b.tag) then begin result := false; exit; end; - result := (a.arrIdx < b.arrIdx); -end; - var TextNameHash: THashStrInt = nil; // key: texture name; value: index in `Textures` BadTextNameHash: THashStrInt = nil; // set; so we won't spam with non-existing texture messages diff --git a/src/game/g_monsters.pas b/src/game/g_monsters.pas index a5c543f..d6d65d3 100644 --- a/src/game/g_monsters.pas +++ b/src/game/g_monsters.pas @@ -328,6 +328,7 @@ var ANIM_ATTACK = 4; ANIM_ATTACK2 = 5; ANIM_PAIN = 6; + ANIM_LAST = ANIM_PAIN; // Таблица характеристик монстров: MONSTERTABLE: Array [MONSTER_DEMON..MONSTER_MAN] of diff --git a/src/game/opengl/r_map.pas b/src/game/opengl/r_map.pas index 0339454..9a6235c 100644 --- a/src/game/opengl/r_map.pas +++ b/src/game/opengl/r_map.pas @@ -17,7 +17,7 @@ unit r_map; interface - uses g_panel, MAPDEF; // TPanel, TDFColor + uses g_panel, MAPDEF, binheap; // TPanel, TDFColor procedure r_Map_Initialize; procedure r_Map_Finalize; @@ -38,6 +38,17 @@ interface procedure r_Panel_Draw (constref p: TPanel; hasAmbient: Boolean; constref ambColor: TDFColor); procedure r_Panel_DrawShadowVolume (constref p: TPanel; lightX, lightY: Integer; radius: Integer); + type + TBinHeapPanelDrawCmp = class + public + class function less (const a, b: TPanel): Boolean; inline; + end; + + TBinHeapPanelDraw = specialize TBinaryHeapBase; + + var + gDrawPanelList: TBinHeapPanelDraw = nil; // binary heap of all walls we have to render, populated by `g_Map_CollectDrawPanels()` + implementation uses @@ -57,6 +68,13 @@ implementation FlagFrames: array [FLAG_RED..FLAG_BLUE] of DWORD; FlagAnim: TAnimState; + class function TBinHeapPanelDrawCmp.less (const a, b: TPanel): Boolean; inline; + begin + if (a.tag < b.tag) then begin result := true; exit; end; + if (a.tag > b.tag) then begin result := false; exit; end; + result := (a.arrIdx < b.arrIdx); + end; + procedure r_Map_Initialize; begin FlagAnim := TAnimState.Create(True, 8, 5); diff --git a/src/game/renders/opengl/r_atlas.pas b/src/game/renders/opengl/r_atlas.pas new file mode 100644 index 0000000..cb5f77d --- /dev/null +++ b/src/game/renders/opengl/r_atlas.pas @@ -0,0 +1,266 @@ +(* Copyright (C) Doom 2D: Forever Developers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License ONLY. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *) +{$INCLUDE ../../../shared/a_modes.inc} +unit r_atlas; + +interface + + type + TAtlasNode = class + private + left, right, up: TAtlasNode; + mL, mT, mR, mB: Integer; + leaf: Boolean; + + public + constructor Create; + destructor Destroy; override; + + procedure Dealloc; + + function GetWidth (): Integer; inline; + function GetHeight (): Integer; inline; + + property x: Integer read mL; + property y: Integer read mT; + property width: Integer read GetWidth; + property height: Integer read GetHeight; + + property l: Integer read mL; + property t: Integer read mT; + property r: Integer read mR; + property b: Integer read mB; + end; + + TAtlas = class + public + constructor Create (w, h: Integer); + destructor Destroy; override; (* also destroed attached nodes *) + + (* never free TAtlasNode directly, use Dealloc method. *) + function CreateNode (): TAtlasNode; virtual; (* allocate empty node (user defined type) *) + function Alloc (w, h: Integer): TAtlasNode; (* allocate node and attach it *) + + function GetWidth (): Integer; inline; + function GetHeight (): Integer; inline; + + private + root: TAtlasNode; + + function NewNode (p: TAtlasNode; w, h: Integer): TAtlasNode; + + public + property w: Integer read GetWidth; + property h: Integer read GetHeight; + end; + + +implementation + + procedure FreeNodeRecursive (n: TAtlasNode); + begin + if n <> nil then + begin + FreeNodeRecursive(n.left); + FreeNodeRecursive(n.right); + n.Free(); + end; + end; + + function IsLeafTree (n: TAtlasNode): Boolean; + begin + result := (n <> nil) and (n.leaf or IsLeafTree(n.left) or IsLeafTree(n.right)) + end; + + (* --------- TNode --------- *) + + constructor TAtlasNode.Create; + begin + inherited; + end; + + destructor TAtlasNode.Destroy; + var p: TAtlasNode; + begin + p := self.up; + if p <> nil then + begin + if p.left = self then + p.left := nil + else if p.right = self then + p.right := nil + end; + self.up := nil; + if self.left <> nil then + self.left.Free; + if self.right <> nil then + self.right.Free; + inherited; + end; + + procedure TAtlasNode.Dealloc; + var p: TAtlasNode; + begin + ASSERT(self.leaf = true); + ASSERT(self.right = nil); + ASSERT(self.left = nil); + self.leaf := false; + p := self.up; + while p <> nil do + begin + ASSERT(p.leaf = false); + ASSERT(p.left <> nil); + ASSERT(p.right <> nil); + if IsLeafTree(p) = false then + begin + FreeNodeRecursive(p.left); p.left := nil; + FreeNodeRecursive(p.right); p.right := nil; + p := p.up + end + else + begin + p := nil + end + end + end; + + function TAtlasNode.GetWidth (): Integer; + begin + result := self.r - self.l + 1; + end; + + function TAtlasNode.GetHeight (): Integer; + begin + result := self.b - self.t + 1; + end; + + (* --------- TAtlas --------- *) + + constructor TAtlas.Create (w, h: Integer); + begin + inherited Create(); + self.root := self.CreateNode(); + ASSERT(self.root <> nil); + self.root.mR := w - 1; + self.root.mB := h - 1; + end; + + destructor TAtlas.Destroy; + begin + FreeNodeRecursive(self.root.left); + FreeNodeRecursive(self.root.right); + inherited; + end; + + function TAtlas.GetWidth (): Integer; + begin + result := self.root.r {- self.root.l} + 1; + end; + + function TAtlas.GetHeight (): Integer; + begin + result := self.root.b {- self.root.t} + 1; + end; + + function TAtlas.CreateNode (): TAtlasNode; + begin + result := TAtlasNode.Create() + end; + + function TAtlas.NewNode (p: TAtlasNode; w, h: Integer): TAtlasNode; + var n: TAtlasNode; + begin + ASSERT(p <> nil); + ASSERT(w > 0); + ASSERT(h > 0); + if p.left <> nil then + begin + ASSERT(p.right <> nil); + n := NewNode(p.left, w, h); + if n = nil then + n := NewNode(p.right, w, h); + result := n; + end + else if p.leaf or (p.width < w) or (p.height < h) then + begin + result := nil; + end + else if (p.width = w) and (p.height = h) then + begin + p.leaf := true; + result := p; + end + else + begin + p.left := self.CreateNode(); + p.right := self.CreateNode(); + if (p.left = nil) or (p.right = nil) then + begin + (* failed to allocate nodes *) + if p.left <> nil then + p.left.Free(); + if p.right <> nil then + p.right.Free(); + p.left := nil; + p.right := nil; + result := nil; + end + else + begin + p.left.up := p; + p.right.up := p; + if p.width - w > p.height - h then + begin + p.left.mL := p.l; + p.left.mT := p.t; + p.left.mR := p.l + w - 1; + p.left.mB := p.b; + p.right.mL := p.l + w; + p.right.mT := p.t; + p.right.mR := p.r; + p.right.mB := p.b; + end + else + begin + p.left.mL := p.l; + p.left.mt := p.t; + p.left.mR := p.r; + p.left.mB := p.t + h - 1; + p.right.mL := p.l; + p.right.mT := p.t + h; + p.right.mR := p.r; + p.right.mB := p.b; + end; + result := NewNode(p.left, w, h); + end + end + end; + + function TAtlas.Alloc (w, h: Integer): TAtlasNode; + var n: TAtlasNode; + begin + ASSERT(w > 0); + ASSERT(h > 0); + n := nil; + if (w <= self.w) and (h <= self.h) then + begin + n := NewNode(self.root, w, h); + if n <> nil then + ASSERT(n.leaf); + end; + result := n + end; + +end. diff --git a/src/game/renders/opengl/r_draw.pas b/src/game/renders/opengl/r_draw.pas new file mode 100644 index 0000000..d76f493 --- /dev/null +++ b/src/game/renders/opengl/r_draw.pas @@ -0,0 +1,211 @@ +(* Copyright (C) Doom 2D: Forever Developers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License ONLY. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *) +{$INCLUDE ../../../shared/a_modes.inc} +unit r_draw; + +interface + + uses + g_textures, + r_textures + ; + + procedure r_Draw_Texture (img: TGLTexture; x, y, w, h: Integer; flip: Boolean); + procedure r_Draw_TextureRepeat (img: TGLTexture; x, y, w, h: Integer; flip: Boolean); + + procedure r_Draw_MultiTextureRepeat (m: TGLMultiTexture; const a: TAnimState; x, y, w, h: Integer; flip: Boolean); + + procedure r_Draw_Filter (l, t, r, b: Integer; rr, gg, bb, aa: Byte); + +implementation + + uses + {$IFDEF USE_GLES1} + GLES11, + {$ELSE} + GL, GLEXT, + {$ENDIF} + SysUtils, Classes, Math, + e_log, utils, + g_game // gScreenWidth, gScreenHeight + ; + + procedure SetupMatrix; + begin + glScissor(0, 0, gScreenWidth, gScreenHeight); + glViewport(0, 0, gScreenWidth, gScreenHeight); + glMatrixMode(GL_PROJECTION); + glLoadIdentity; + glOrtho(0, gScreenWidth, gScreenHeight, 0, 0, 1); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity; + end; + + procedure DrawQuad (x, y, w, h: Integer); + begin + glBegin(GL_QUADS); + glVertex2i(x + w, y); + glVertex2i(x, y); + glVertex2i(x, y + h); + glVertex2i(x + w, y + h); + glEnd(); + end; + + procedure DrawTile (tile: TGLAtlasNode; x, y, w, h: Integer; flip: Boolean); + var nw, nh, ax, bx, ay, by: GLfloat; l, t, r, b: Integer; + begin + if tile = nil then + begin + glColor3ub(255, 0, 0); + glDisable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_TEXTURE_2D); + DrawQuad(x, y, w, h); + end + else + begin + nw := tile.base.w; + nh := tile.base.h; + ax := IfThen(flip, tile.l, tile.r + 1) / nw; + bx := IfThen(flip, tile.r + 1, tile.l) / nh; + ay := (tile.t) / nw; + by := (tile.b + 1) / nh; + l := x; t := y; r := x + w; b := y + h; + glColor3ub(255, 255, 255); + glBindTexture(GL_TEXTURE_2D, tile.id); + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glBegin(GL_QUADS); + glTexCoord2f(ax, ay); glVertex2i(r, t); + glTexCoord2f(bx, ay); glVertex2i(l, t); + glTexCoord2f(bx, by); glVertex2i(l, b); + glTexCoord2f(ax, by); glVertex2i(r, b); + glEnd(); + glDisable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, 0); + end + end; + +(* + procedure r_Draw_Texture (img: TGLTexture; x, y, w, h: Integer; flip: Boolean); + var i, j, offx, offy, nw, nh: Integer; n: TGLAtlasNode; + begin + ASSERT(w >= 0); + ASSERT(h >= 0); + if img = nil then + DrawTile(nil, x, y, w, h, flip) + else + begin + offx := 0; + offy := 0; + nw := w div img.cols; + nh := h div img.lines; + for j := 0 to img.lines - 1 do + begin + for i := 0 to img.cols - 1 do + begin + n := img.GetTile(i, j); + ASSERT(n <> nil); + DrawTile(n, x + offx, y + offy, nw, nh, flip); + offx := offx + nw; + end; + offx := 0; + offy := offy + nh; + end + end + end; +*) + + procedure r_Draw_Texture (img: TGLTexture; x, y, w, h: Integer; flip: Boolean); + var i, j, offx, offy: Integer; n: TGLAtlasNode; + begin + ASSERT(w >= 0); + ASSERT(h >= 0); + if img = nil then + DrawTile(nil, x, y, w, h, flip) + else + begin + glPushMatrix; + glScalef(w / img.width, h / img.height, 1); + offx := 0; + offy := 0; + for j := 0 to img.lines - 1 do + begin + for i := 0 to img.cols - 1 do + begin + n := img.GetTile(i, j); + ASSERT(n <> nil); + DrawTile(n, x + offx, y + offy, n.width, n.height, flip); + offx := offx + n.width; + end; + offx := 0; + offy := offy + n.height; + end; + glPopMatrix; + end + end; + + procedure r_Draw_TextureRepeat (img: TGLTexture; x, y, w, h: Integer; flip: Boolean); + var i, j: Integer; + begin + ASSERT(w >= 0); + ASSERT(h >= 0); + if img = nil then + r_Draw_Texture(nil, x, y, w, h, flip) + else + for j := 0 to h div img.height - 1 do + for i := 0 to w div img.width - 1 do + r_Draw_Texture(img, x + i * img.width, y + j * img.height, img.width, img.height, flip); + end; + + procedure r_Draw_MultiTextureRepeat (m: TGLMultiTexture; const a: TAnimState; x, y, w, h: Integer; flip: Boolean); + var img: TGLTexture; cur, total, i: Integer; + begin + ASSERT(a.IsValid()); + if m = nil then + r_Draw_TextureRepeat(nil, x, y, w, h, flip) + else + begin + if m.BackAnim then + begin + total := m.count * 2 - 1; + cur := a.CurrentFrame mod total; + if cur < m.count then i := cur else i := total - cur - 1; + end + else + i := a.CurrentFrame mod m.count; + img := m.GetTexture(i); + r_Draw_TextureRepeat(img, x, y, w, h, flip) + end + end; + + procedure r_Draw_Filter (l, t, r, b: Integer; rr, gg, bb, aa: Byte); + begin + ASSERT(r >= l); + ASSERT(b >= t); + glEnable(GL_BLEND); + glBlendFunc(GL_ZERO, GL_SRC_COLOR); + glDisable(GL_TEXTURE_2D); + glColor4ub(rr, gg, bb, aa); + glBegin(GL_QUADS); + glVertex2i(l, t); + glVertex2i(r, t); + glVertex2i(r, b); + glVertex2i(l, b); + glEnd; + end; + +end. diff --git a/src/game/renders/opengl/r_map.pas b/src/game/renders/opengl/r_map.pas new file mode 100644 index 0000000..016aed3 --- /dev/null +++ b/src/game/renders/opengl/r_map.pas @@ -0,0 +1,612 @@ +(* Copyright (C) Doom 2D: Forever Developers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License ONLY. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *) +{$INCLUDE ../../../shared/a_modes.inc} +unit r_map; + +interface + + procedure r_Map_Initialize; + procedure r_Map_Finalize; + + procedure r_Map_Load; + procedure r_Map_Free; + + procedure r_Map_LoadTextures; + procedure r_Map_FreeTextures; + + procedure r_Map_Update; + + procedure r_Map_Draw (x, y, w, h, camx, camy: Integer); + +implementation + + uses + {$IFDEF USE_GLES1} + GLES11, + {$ELSE} + GL, GLEXT, + {$ENDIF} + e_log, + binheap, MAPDEF, utils, + g_options, g_textures, g_basic, g_base, g_phys, + g_game, g_map, g_panel, g_items, g_monsters, g_playermodel, g_player, + {$IFDEF ENABLE_CORPSES} + g_corpses, + {$ENDIF} + r_textures, r_draw + ; + + const + MTABLE: array [0..MONSTER_MAN] of record + w, h: Integer; + end = ( + (w: 64; h: 64), // NONE + (w: 64; h: 64), // DEMON + (w: 64; h: 64), // IMP + (w: 64; h: 64), // ZOMBY + (w: 64; h: 64), // SERG + (w: 128; h: 128), // CYBER + (w: 64; h: 64), // CGUN + (w: 128; h: 128), // BARON + (w: 128; h: 128), // KNIGHT + (w: 128; h: 128), // CACO + (w: 64; h: 64), // SOUL (DIE is 128x128, see load code) + (w: 128; h: 128), // PAIN + (w: 256; h: 128), // SPIDER + (w: 128; h: 64), // BSP + (w: 128; h: 128), // MANCUB + (w: 128; h: 128), // SKEL + (w: 128; h: 128), // VILE + (w: 32; h: 32), // FISH + (w: 64; h: 64), // BARREL + (w: 128; h: 128), // ROBO + (w: 64; h: 64) // MAN + ); + VILEFIRE_DX = 32; + VILEFIRE_DY = 128; + + type + TBinHeapPanelDrawCmp = class + public + class function less (const a, b: TPanel): Boolean; inline; + end; + + TBinHeapPanelDraw = specialize TBinaryHeapBase; + + TMonsterAnims = array [0..ANIM_LAST, TDirection] of TGLMultiTexture; + + var + SkyTexture: TGLTexture; + RenTextures: array of record + spec: LongInt; + tex: TGLMultiTexture; + end; + Items: array [0..ITEM_MAX] of record + tex: TGLMultiTexture; + anim: TAnimState; + end; + MonTextures: array [0..MONSTER_MAN] of TMonsterAnims; + VileFire: TGLMultiTexture; + Models: array of record + anim: array [TDirection, 0..A_LAST] of record + base, mask: TGLMultiTexture; + end; +(* + {$IFDEF ENABLE_GIBS} + gibs: array of record + base, mask: TGLTexture; + rect: TRectWH; + end; + {$ENDIF} +*) + end; + + plist: TBinHeapPanelDraw = nil; + + class function TBinHeapPanelDrawCmp.less (const a, b: TPanel): Boolean; inline; + begin + if a.tag < b.tag then begin result := true; exit; end; + if a.tag > b.tag then begin result := false; exit; end; + result := a.arrIdx < b.arrIdx; + end; + + procedure r_Map_Initialize; + begin + plist := TBinHeapPanelDraw.Create(); + end; + + procedure r_Map_Finalize; + begin + plist.Free + end; + + procedure r_Map_LoadModel (i: Integer); + var prefix: AnsiString; a: Integer; d: TDirection; m: ^TPlayerModelInfo; + begin + m := @PlayerModelsArray[i]; + prefix := m.FileName + ':TEXTURES/'; + for d := TDirection.D_LEFT to TDirection.D_RIGHT do + begin + for a := A_STAND to A_LAST do + begin + Models[i].anim[d, a].base := nil; + Models[i].anim[d, a].mask := nil; + if m.anim[d, a].resource <> '' then + Models[i].anim[d, a].base := r_Textures_LoadMultiFromFileAndInfo(prefix + m.anim[d, a].resource, 64, 64, m.anim[d, a].frames, m.anim[d, a].back, true); + if m.anim[d, a].mask <> '' then + Models[i].anim[d, a].mask := r_Textures_LoadMultiFromFileAndInfo(prefix + m.anim[d, a].mask, 64, 64, m.anim[d, a].frames, m.anim[d, a].back, true); + end + end; +(* + {$IFDEF ENABLE_GIBS} + Models[i].gibs := nil; + if m.GibsCount > 0 then + begin + SetLength(Models[i].gibs, m.GibsCount); + end; + {$ENDIF} +*) + end; + + + procedure r_Map_Load; + var i, j: Integer; d: TDirection; + + procedure LoadItem (i: Integer; const name: AnsiString; w, h, delay, count: Integer; backanim: Boolean); + begin + ASSERT(i >= 0); + ASSERT(i <= ITEM_MAX); + Items[i].tex := r_Textures_LoadMultiFromFileAndInfo(GameWAD + ':TEXTURES/' + name, w, h, count, backanim, false); + if backanim then count := count * 2 - 2; + Items[i].anim := TAnimState.Create(True, delay, count); + ASSERT(Items[i].tex <> NIL); + end; + + procedure LoadMonster (m, a: Integer; d: TDirection); + const + dir: array [TDirection] of AnsiString = ('_L', ''); + var + w, h, count: Integer; + begin + count := MONSTER_ANIMTABLE[m].AnimCount[a]; + if count > 0 then + begin + w := MTABLE[m].w; + h := MTABLE[m].h; + if (m = MONSTER_SOUL) and (a = ANIM_DIE) then + begin + // special case + w := 128; + h := 128; + end; + MonTextures[m, a, d] := r_Textures_LoadMultiFromFileAndInfo( + GameWAD + ':MTEXTURES/' + MONSTERTABLE[m].name + '_' + ANIMTABLE[a].name + dir[d], + w, + h, + count, + False, + False + ) + end + else + MonTextures[m, a, d] := nil + end; + + begin + // --------- items --------- // + // i name w h d n backanim + LoadItem(ITEM_NONE, 'NOTEXTURE', 16, 16, 0, 1, False); + LoadItem(ITEM_MEDKIT_SMALL, 'MED1', 16, 16, 0, 1, False); + LoadItem(ITEM_MEDKIT_LARGE, 'MED2', 32, 32, 0, 1, False); + LoadItem(ITEM_MEDKIT_BLACK, 'BMED', 32, 32, 0, 1, False); + LoadItem(ITEM_ARMOR_GREEN, 'ARMORGREEN', 32, 16, 20, 3, True); + LoadItem(ITEM_ARMOR_BLUE, 'ARMORBLUE', 32, 16, 20, 3, True); + LoadItem(ITEM_SPHERE_BLUE, 'SBLUE', 32, 32, 15, 4, True); + LoadItem(ITEM_SPHERE_WHITE, 'SWHITE', 32, 32, 20, 4, True); + LoadItem(ITEM_SUIT, 'SUIT', 32, 64, 0, 1, False); + LoadItem(ITEM_OXYGEN, 'OXYGEN', 16, 32, 0, 1, False); + LoadItem(ITEM_INVUL, 'INVUL', 32, 32, 20, 4, True); + LoadItem(ITEM_WEAPON_SAW, 'SAW', 64, 32, 0, 1, False); + LoadItem(ITEM_WEAPON_SHOTGUN1, 'SHOTGUN1', 64, 16, 0, 1, False); + LoadItem(ITEM_WEAPON_SHOTGUN2, 'SHOTGUN2', 64, 16, 0, 1, False); + LoadItem(ITEM_WEAPON_CHAINGUN, 'MGUN', 64, 16, 0, 1, False); + LoadItem(ITEM_WEAPON_ROCKETLAUNCHER, 'RLAUNCHER', 64, 16, 0, 1, False); + LoadItem(ITEM_WEAPON_PLASMA, 'PGUN', 64, 16, 0, 1, False); + LoadItem(ITEM_WEAPON_BFG, 'BFG', 64, 64, 0, 1, False); + LoadItem(ITEM_WEAPON_SUPERPULEMET, 'SPULEMET', 64, 16, 0, 1, False); + LoadItem(ITEM_AMMO_BULLETS, 'CLIP', 16, 16, 0, 1, False); + LoadItem(ITEM_AMMO_BULLETS_BOX, 'AMMO', 32, 16, 0, 1, False); + LoadItem(ITEM_AMMO_SHELLS, 'SHELL1', 16, 8, 0, 1, False); + LoadItem(ITEM_AMMO_SHELLS_BOX, 'SHELL2', 32, 16, 0, 1, False); + LoadItem(ITEM_AMMO_ROCKET, 'ROCKET', 16, 32, 0, 1, False); + LoadItem(ITEM_AMMO_ROCKET_BOX, 'ROCKETS', 64, 32, 0, 1, False); + LoadItem(ITEM_AMMO_CELL, 'CELL', 16, 16, 0, 1, False); + LoadItem(ITEM_AMMO_CELL_BIG, 'CELL2', 32, 32, 0, 1, False); + LoadItem(ITEM_AMMO_BACKPACK, 'BPACK', 32, 32, 0, 1, False); + LoadItem(ITEM_KEY_RED, 'KEYR', 16, 16, 0, 1, False); + LoadItem(ITEM_KEY_GREEN, 'KEYG', 16, 16, 0, 1, False); + LoadItem(ITEM_KEY_BLUE, 'KEYB', 16, 16, 0, 1, False); + LoadItem(ITEM_WEAPON_KASTET, 'KASTET', 64, 32, 0, 1, False); + LoadItem(ITEM_WEAPON_PISTOL, 'PISTOL', 64, 16, 0, 1, False); + LoadItem(ITEM_BOTTLE, 'BOTTLE', 16, 32, 20, 4, True); + LoadItem(ITEM_HELMET, 'HELMET', 16, 16, 20, 4, True); + LoadItem(ITEM_JETPACK, 'JETPACK', 32, 32, 15, 3, True); + LoadItem(ITEM_INVIS, 'INVIS', 32, 32, 20, 4, True); + LoadItem(ITEM_WEAPON_FLAMETHROWER, 'FLAMETHROWER', 64, 32, 0, 1, False); + LoadItem(ITEM_AMMO_FUELCAN, 'FUELCAN', 16, 32, 0, 1, False); + // fill with NOTEXURE forgotten item + for i := ITEM_AMMO_FUELCAN + 1 to ITEM_MAX do + LoadItem(i,'NOTEXTURE', 16, 16, 0, 1, False); + // --------- monsters --------- // + for i := MONSTER_DEMON to MONSTER_MAN do + for j := 0 to ANIM_LAST do + for d := TDirection.D_LEFT to TDirection.D_RIGHT do + LoadMonster(i, j, d); + VileFire := r_Textures_LoadMultiFromFileAndInfo(GameWAD + ':TEXTURES/FIRE', 64, 128, 8, False, False); + // --------- player models --------- // + if PlayerModelsArray <> nil then + begin + SetLength(Models, Length(PlayerModelsArray)); + for i := 0 to High(PlayerModelsArray) do + r_Map_LoadModel(i); + end; + end; + + procedure r_Map_Free; + var i, j: Integer; d: TDirection; + begin + for i := MONSTER_DEMON to MONSTER_MAN do + begin + for j := 0 to ANIM_LAST do + begin + for d := TDirection.D_LEFT to TDirection.D_RIGHT do + begin + if MonTextures[i, j, d] <> nil then + MonTextures[i, j, d].Free; + MonTextures[i, j, d] := nil; + end; + end; + end; + for i := 0 to ITEM_MAX do + begin + if Items[i].tex <> nil then + begin + Items[i].tex.Free; + Items[i].tex := nil; + end; + Items[i].anim.Invalidate; + end; + end; + + procedure r_Map_LoadTextures; + var i, n: Integer; + begin + if Textures <> nil then + begin + n := Length(Textures); + SetLength(RenTextures, n); + for i := 0 to n - 1 do + begin + RenTextures[i].tex := nil; + case Textures[i].TextureName of + TEXTURE_NAME_WATER: RenTextures[i].spec := TEXTURE_SPECIAL_WATER; + TEXTURE_NAME_ACID1: RenTextures[i].spec := TEXTURE_SPECIAL_ACID1; + TEXTURE_NAME_ACID2: RenTextures[i].spec := TEXTURE_SPECIAL_ACID2; + else + RenTextures[i].spec := 0; + RenTextures[i].tex := r_Textures_LoadMultiFromFile(Textures[i].FullName); + if RenTextures[i].tex = nil then + e_LogWritefln('r_Map_LoadTextures: failed to load texture: %s', [Textures[i].FullName]); + end; + end; + end; + if gMapInfo.SkyFullName <> '' then + SkyTexture := r_Textures_LoadFromFile(gMapInfo.SkyFullName); + plist.Clear; + end; + + procedure r_Map_FreeTextures; + var i: Integer; + begin + plist.Clear; + if SkyTexture <> nil then + SkyTexture.Free; + SkyTexture := nil; + if RenTextures <> nil then + for i := 0 to High(RenTextures) do + if RenTextures[i].tex <> nil then + RenTextures[i].tex.Free; + RenTextures := nil; + end; + + procedure r_Map_Update; + var i: Integer; + begin + for i := 0 to ITEM_MAX do + Items[i].anim.Update; + end; + + procedure r_Map_DrawPanel (p: TPanel); + var Texture: Integer; t: TGLMultiTexture; + begin + ASSERT(p <> nil); + if p.FCurTexture >= 0 then + begin + Texture := p.TextureIDs[p.FCurTexture].Texture; + t := RenTextures[Texture].tex; + + if (RenTextures[Texture].spec = 0) or (t <> nil) then + begin + // TODO set alpha and blending type + if t = nil then + r_Draw_TextureRepeat(nil, p.x, p.y, p.width, p.height, false) + else if p.TextureIDs[p.FCurTexture].AnTex.IsValid() then + r_Draw_MultiTextureRepeat(t, p.TextureIDs[p.FCurTexture].AnTex, p.x, p.y, p.width, p.height, false) + else + r_Draw_TextureRepeat(t.GetTexture(0), p.x, p.y, p.width, p.height, false) + end; + + if t = nil then + begin + case RenTextures[Texture].spec of + TEXTURE_SPECIAL_WATER: r_Draw_Filter(p.x, p.y, p.x + p.width, p.y + p.height, 0, 0, 255, 255); + TEXTURE_SPECIAL_ACID1: r_Draw_Filter(p.x, p.y, p.x + p.width, p.y + p.height, 0, 230, 0, 255); + TEXTURE_SPECIAL_ACID2: r_Draw_Filter(p.x, p.y, p.x + p.width, p.y + p.height, 230, 0, 0, 255); + end + end + end + end; + + procedure r_Map_DrawPanelType (panelTyp: DWORD); + var tagMask, i: Integer; p: TPanel; + begin + i := 0; + tagMask := PanelTypeToTag(panelTyp); + while plist.count > 0 do + begin + p := TPanel(plist.Front()); + if (p.tag and tagMask) = 0 then + break; + r_Map_DrawPanel(p); + Inc(i); + plist.PopFront + end; + end; + + procedure r_Map_DrawItems (x, y, w, h: Integer; drop: Boolean); + var i, fX, fY: Integer; it: PItem; t: TGLMultiTexture; + begin + if ggItems <> nil then + begin + for i := 0 to High(ggItems) do + begin + it := @ggItems[i]; + if it.used and it.alive and (it.dropped = drop) and (it.ItemType <> ITEM_NONE) then + begin + t := Items[it.ItemType].tex; + if g_Collide(it.obj.x, it.obj.y, t.width, t.height, x, y, w, h) then + begin + it.obj.Lerp(gLerpFactor, fX, fY); + r_Draw_MultiTextureRepeat(t, Items[it.ItemType].anim, fX, fY, t.width, t.height, false); + // if g_debug_frames then // TODO draw collision frame + end; + end; + end; + end; + end; + + function r_Map_GetMonsterTexture (m, a: Integer; d: TDirection; out t: TGLMultiTexture; out dx, dy: Integer; out flip: Boolean): Boolean; + // var c: Integer; + begin + t := nil; dx := 0; dy := 0; flip := false; + result := MonTextures[m, a, d] <> nil; + if result = false then + begin + flip := true; + if d = TDirection.D_RIGHT then d := TDirection.D_LEFT else d := TDirection.D_RIGHT; + result := MonTextures[m, a, d] <> nil; + end; + if result = true then + begin + t := MonTextures[m, a, d]; + if d = TDirection.D_LEFT then + begin + dx := MONSTER_ANIMTABLE[m].AnimDeltaLeft[a].X; + dy := MONSTER_ANIMTABLE[m].AnimDeltaLeft[a].Y; + end + else + begin + dx := MONSTER_ANIMTABLE[m].AnimDeltaRight[a].X; + dy := MONSTER_ANIMTABLE[m].AnimDeltaRight[a].Y; + end; + if flip then + begin +// c := (MONSTERTABLE[MonsterType].Rect.X - dx) + MONSTERTABLE[MonsterType].Rect.Width; +// dx := MTABLE[m].width - c - MONSTERTABLE[MonsterType].Rect.X; + dx := -dx; + end; + end; + end; + + procedure r_Map_DrawMonsterAttack (constref mon: TMonster); + var o: TObj; + begin + if VileFire <> nil then + if (mon.MonsterType = MONSTER_VILE) and (mon.MonsterState = MONSTATE_SHOOT) then + if mon.VileFireAnim.IsValid() and GetPos(mon.MonsterTargetUID, @o) then + r_Draw_MultiTextureRepeat(VileFire, mon.VileFireAnim, o.x + o.rect.x + (o.rect.width div 2) - VILEFIRE_DX, o.y + o.rect.y + o.rect.height - VILEFIRE_DY, VileFire.width, VileFire.height, False); + end; + + procedure r_Map_DrawMonster (constref mon: TMonster); + var m, a, fX, fY, dx, dy: Integer; d: TDirection; flip: Boolean; t: TGLMultiTexture; + begin + m := mon.MonsterType; + a := mon.MonsterAnim; + d := mon.GameDirection; + + mon.obj.Lerp(gLerpFactor, fX, fY); + + if r_Map_GetMonsterTexture(m, a, d, t, dx, dy, flip) then + r_Draw_MultiTextureRepeat(t, mon.DirAnim[a, d], fX + dx, fY + dy, t.width, t.height, flip); + +{ + if g_debug_frames + // TODO draw frame +} + end; + + procedure r_Map_DrawMonsters (x, y, w, h: Integer); + var i: Integer; m: TMonster; + begin + if gMonsters <> nil then + begin + for i := 0 to High(gMonsters) do + begin + m := gMonsters[i]; + if m <> nil then + begin + r_Map_DrawMonsterAttack(m); + // TODO select from grid + if g_Collide(m.obj.x + m.obj.rect.x, m.obj.y + m.obj.rect.y, m.obj.rect.width, m.obj.rect.height, x, y, w, h) then + r_Map_DrawMonster(m); + end; + end; + end; + end; + + function r_Map_GetPlayerModelTex (i: Integer; var a: Integer; var d: TDirection; out flip: Boolean): Boolean; + begin + flip := false; + result := Models[i].anim[d, a].base <> nil; + if result = false then + begin + flip := true; + if d = TDirection.D_LEFT then d := TDirection.D_RIGHT else d := TDirection.D_LEFT; + result := Models[i].anim[d, a].base <> nil; + end; + end; + + procedure r_Map_DrawPlayerModel (pm: TPlayerModel; x, y: Integer); + var a: Integer; d: TDirection; flip: Boolean; t: TGLMultiTexture; + begin + // TODO draw flag + // TODO draw weapon + a := pm.CurrentAnimation; + d := pm.Direction; + if r_Map_GetPlayerModelTex(pm.id, a, d, flip) then + begin + t := Models[pm.id].anim[d, a].base; + r_Draw_MultiTextureRepeat(t, pm.AnimState, x, y, t.width, t.height, flip); + // TODO colorize mask + t := Models[pm.id].anim[d, a].mask; + if t <> nil then + r_Draw_MultiTextureRepeat(t, pm.AnimState, x, y, t.width, t.height, flip); + end; + end; + + procedure r_Map_DrawPlayer (p: TPlayer); + var fX, fY, fSlope: Integer; + begin + if p.alive then + begin + fX := p.obj.x; fY := p.obj.y; + // TODO fix lerp + //p.obj.Lerp(gLerpFactor, fX, fY); + fSlope := nlerp(p.SlopeOld, p.obj.slopeUpLeft, gLerpFactor); + // TODO draw punch + // TODO invul pentagram + // TODO draw it with transparency + r_Map_DrawPlayerModel(p.Model, fX, fY + fSlope); + end; + // TODO draw g_debug_frames + // TODO draw chat bubble + // TODO draw aim + end; + + procedure r_Map_DrawPlayers (x, y, w, h: Integer); + var i: Integer; + begin + // TODO draw only visible + if gPlayers <> nil then + for i := 0 to High(gPlayers) do + if gPlayers[i] <> nil then + r_Map_DrawPlayer(gPlayers[i]); + end; + +{$IFDEF ENABLE_CORPSES} + procedure r_Map_DrawCorpses (x, y, w, h: Integer); + var i, fX, fY: Integer; p: TCorpse; + begin + if gCorpses <> nil then + begin + for i := 0 to High(gCorpses) do + begin + p := gCorpses[i]; + if (p <> nil) and (p.state <> CORPSE_STATE_REMOVEME) and (p.model <> nil) then + begin + p.obj.Lerp(gLerpFactor, fX, fY); + r_Map_DrawPlayerModel(p.model, fX, fY); + end; + end; + end; + end; +{$ENDIF} + + procedure r_Map_Draw (x, y, w, h, camx, camy: Integer); + var iter: TPanelGrid.Iter; p: PPanel; cx, cy, xx, yy, ww, hh: Integer; + begin + cx := camx - w div 2; + cy := camy - h div 2; + xx := x + cx; + yy := y + cy; + ww := w; + hh := h; + + if SkyTexture <> nil then + r_Draw_Texture(SkyTexture, x, y, w, h, false); + + plist.Clear; + iter := mapGrid.ForEachInAABB(xx, yy, ww, hh, GridDrawableMask); + for p in iter do + if ((p^.tag and GridTagDoor) <> 0) = p^.door then + plist.Insert(p^); + iter.Release; + + glPushMatrix; + glTranslatef(-cx, -cy, 0); + r_Map_DrawPanelType(PANEL_BACK); + r_Map_DrawPanelType(PANEL_STEP); + r_Map_DrawItems(xx, yy, ww, hh, false); + // TODO draw weapons + // TODO draw shells + r_Map_DrawPlayers(xx, yy, ww, hh); + // TODO draw gibs + {$IFDEF ENABLE_CORPSES} + r_Map_DrawCorpses(xx, yy, ww, hh); + {$ENDIF} + r_Map_DrawPanelType(PANEL_WALL); + r_Map_DrawMonsters(xx, yy, ww, hh); + r_Map_DrawItems(xx, yy, ww, hh, true); + r_Map_DrawPanelType(PANEL_CLOSEDOOR); + // TODO draw gfx + // TODO draw flags + r_Map_DrawPanelType(PANEL_ACID1); + r_Map_DrawPanelType(PANEL_ACID2); + r_Map_DrawPanelType(PANEL_WATER); + r_Map_DrawPanelType(PANEL_FORE); + glPopMatrix; + end; + +end. diff --git a/src/game/renders/opengl/r_render.pas b/src/game/renders/opengl/r_render.pas new file mode 100644 index 0000000..568b8b8 --- /dev/null +++ b/src/game/renders/opengl/r_render.pas @@ -0,0 +1,300 @@ +(* Copyright (C) Doom 2D: Forever Developers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License ONLY. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *) +{$INCLUDE ../../../shared/a_modes.inc} +unit r_render; + +interface + + uses + {$IFDEF ENABLE_MENU} + g_gui, + {$ENDIF} + g_base // TRectWH + ; + + (* render startup *) + procedure r_Render_Initialize; + procedure r_Render_Finalize; + + (* load globally used textures *) + procedure r_Render_Load; + procedure r_Render_Free; + + (* load map specific textures *) + procedure r_Render_LoadTextures; + procedure r_Render_FreeTextures; + + procedure r_Render_Update; + procedure r_Render_Draw; + + procedure r_Render_Resize (w, h: Integer); + procedure r_Render_Apply; + + function r_Render_WriteScreenShot (filename: String): Boolean; + + {$IFDEF ENABLE_GIBS} + function r_Render_GetGibRect (m, id: Integer): TRectWH; + {$ENDIF} + + {$IFDEF ENABLE_GFX} + procedure r_Render_QueueEffect (AnimType, X, Y: Integer); + {$ENDIF} + + {$IFDEF ENABLE_TOUCH} + // touch screen button location and size + procedure r_Render_GetKeyRect (key: Integer; out x, y, w, h: Integer; out founded: Boolean); + {$ENDIF} + + {$IFDEF ENABLE_MENU} + procedure r_Render_GetControlSize (ctrl: TGUIControl; out w, h: Integer); + procedure r_Render_GetLogoSize (out w, h: Integer); + procedure r_Render_GetMaxFontSize (BigFont: Boolean; out w, h: Integer); + procedure r_Render_GetStringSize (BigFont: Boolean; str: String; out w, h: Integer); + {$ENDIF} + + procedure r_Render_DrawLoading (force: Boolean); // !!! remove it + +implementation + + uses + {$IFDEF USE_GLES1} + GLES11, + {$ELSE} + GL, GLEXT, + {$ENDIF} + {$IFDEF ENABLE_SYSTEM} + g_system, + {$ENDIF} + SysUtils, Classes, Math, + e_log, utils, + g_game, g_options, g_console, + r_textures, r_map + ; + + var + menuBG: TGLTexture; + + procedure r_Render_LoadTextures; + begin + r_Map_LoadTextures; + end; + + procedure r_Render_FreeTextures; + begin + r_Map_FreeTextures; + end; + + procedure r_Render_Load; + begin + menuBG := r_Textures_LoadFromFile(GameWAD + ':' + 'TEXTURES/TITLE'); + r_Map_Load; + end; + + procedure r_Render_Free; + begin + r_Map_Free; + menuBG.Free; + end; + +{$IFDEF ENABLE_SYSTEM} + function GetInfo (): TGLDisplayInfo; + var info: TGLDisplayInfo; + begin + info := Default(TGLDisplayInfo); + info.w := Max(1, gRC_Width); + info.h := Max(1, gRC_Height); + info.bpp := Max(1, gBPP); + info.fullscreen := gRC_FullScreen; + info.maximized := gRC_Maximized; + info.major := 1; + info.minor := 1; + info.profile := TGLProfile.Compat; + result := info; + end; +{$ENDIF} + + procedure r_Render_Initialize; + begin + {$IFDEF ENABLE_SYSTEM} + if sys_SetDisplayModeGL(GetInfo()) = False then + raise Exception.Create('Failed to set videomode on startup.'); + sys_EnableVSync(gVSync); + {$ENDIF} + r_Textures_Initialize; + r_Map_Initialize; + end; + + procedure r_Render_Finalize; + begin + r_Map_Finalize; + r_Textures_Finalize; + end; + + procedure r_Render_Update; + begin + r_Map_Update; + end; + + procedure SetupMatrix; + begin + glViewport(0, 0, gScreenWidth, gScreenHeight); + glScissor(0, 0, gScreenWidth, gScreenHeight); + glMatrixMode(GL_PROJECTION); + glLoadIdentity; + glOrtho(0, gScreenWidth, gScreenHeight, 0, 0, 1); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity; + end; + + procedure r_Render_Draw; + begin + if gExit = EXIT_QUIT then + exit; + + SetupMatrix; + + glClearColor(0.0, 0.0, 0.0, 0.0); + glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT); + + glColor4ub(255, 255, 255, 255); + + e_LogWritefln('r_render_draw: %sx%s', [gScreenWidth, gScreenHeight]); + + if gGameOn or (gState = STATE_FOLD) then + begin + // TODO setup player view + // TODO setup sectator mode + // TODO setup player hear point + // TODO setup player view siz + + // TODO draw player view + setup screen coords + r_Map_Draw(0, 0, gScreenWidth, gScreenHeight, gPlayer1.obj.x + 32, gPlayer1.obj.y + 32); // !!! remove unnamed consts + + // TODO draw holmes inspector + + // TODO draw messages + // TODO draw stats (?) + // TODO draw spectator hud + end; + + if gPauseMain and gGameOn {$IFDEF ENABLE_MENU}and (g_ActiveWindow = nil){$ENDIF} then + begin + // TODO draw pause screen + end; + + if not gGameOn then + begin + case gState of + STATE_MENU: ; // TODO draw menu bg + STATE_FOLD: ; + STATE_INTERCUSTOM: ; + STATE_INTERSINGLE: ; + STATE_ENDPIC: ; + STATE_SLIST: ; + end; + end; + + {$IFDEF ENABLE_MENU} + if g_ActiveWindow <> nil then + begin + // TODO draw menu widgets + end; + {$ENDIF} + + // TODO draw console + + // TODO draw holmes interface + + glFinish(); + glFlush(); + sys_Repaint; + end; + + procedure r_Render_Resize (w, h: Integer); + begin + gWinSizeX := w; + gWinSizeY := h; + gRC_Width := w; + gRC_Height := h; + gScreenWidth := w; + gScreenHeight := h; + end; + + procedure r_Render_Apply; + begin + {$IFDEF ENABLE_SYSTEM} + if sys_SetDisplayModeGL(GetInfo()) then + e_LogWriteln('resolution changed') + else + e_LogWriteln('resolution not changed'); + sys_EnableVSync(gVSync) + {$ENDIF} + end; + + function r_Render_WriteScreenShot (filename: String): Boolean; + begin + Result := False; + end; + +{$IFDEF ENABLE_GIBS} + function r_Render_GetGibRect (m, id: Integer): TRectWH; + begin + Result.X := 16; + Result.Y := 16; + Result.Width := 16; + Result.Height := 16; + end; +{$ENDIF} + +{$IFDEF ENABLE_GFX} + procedure r_Render_QueueEffect (AnimType, X, Y: Integer); + begin + end; +{$ENDIF} + +{$IFDEF ENABLE_TOUCH} + procedure r_Render_GetKeyRect (key: Integer; out x, y, w, h: Integer; out founded: Boolean); + begin + founded := False; + end; +{$ENDIF} + +{$IFDEF ENABLE_MENU} + procedure r_Render_GetControlSize (ctrl: TGUIControl; out w, h: Integer); + begin + w := 0; h := 0; + end; + + procedure r_Render_GetLogoSize (out w, h: Integer); + begin + w := 0; h := 0; + end; + + procedure r_Render_GetMaxFontSize (BigFont: Boolean; out w, h: Integer); + begin + w := 0; h := 0; + end; + + procedure r_Render_GetStringSize (BigFont: Boolean; str: String; out w, h: Integer); + begin + w := 0; h := 0; + end; +{$ENDIF} + + procedure r_Render_DrawLoading (force: Boolean); + begin + end; + +end. diff --git a/src/game/renders/opengl/r_textures.pas b/src/game/renders/opengl/r_textures.pas new file mode 100644 index 0000000..d24c2ac --- /dev/null +++ b/src/game/renders/opengl/r_textures.pas @@ -0,0 +1,574 @@ +(* Copyright (C) Doom 2D: Forever Developers + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License ONLY. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *) +{$INCLUDE ../../../shared/a_modes.inc} +unit r_textures; + +interface + + uses + {$IFDEF USE_GLES1} + GLES11, + {$ELSE} + GL, GLEXT, + {$ENDIF} + r_atlas, + utils // SSArray + ; + + type + TGLAtlas = class; + + TGLAtlasNode = class (TAtlasNode) + private + mBase: TGLAtlas; + + public + constructor Create (base: TGLAtlas); + destructor Destroy; override; + + function GetID (): GLuint; + + property base: TGLAtlas read mBase; + property id: GLuint read GetID; + end; + + TGLAtlas = class (TAtlas) + private + mID: GLuint; + + public + constructor Create (ww, hh: Integer; id: GLuint); + destructor Destroy; override; + + function CreateNode (): TGLAtlasNode; override; + function Alloc (ww, hh: Integer): TGLAtlasNode; overload; + + property id: GLuint read mID write mID default 0; + end; + + TGLTexture = class + private + mWidth: Integer; + mHeight: Integer; + mCols: Integer; + mTile: array of TGLAtlasNode; + + public + destructor Destroy; override; + + function GetTile (col, line: Integer): TGLAtlasNode; + + function GetLines (): Integer; inline; + + property width: Integer read mWidth; + property height: Integer read mHeight; + property cols: Integer read mCols; + property lines: Integer read GetLines; + end; + + TGLMultiTexture = class + private + mTexture: array of TGLTexture; + mBackanim: Boolean; + + public + destructor Destroy; override; + + function GetWidth (): Integer; inline; + function GetHeight (): Integer; inline; + function GetCount (): Integer; inline; + function GetTexture (i: Integer): TGLTexture; {inline;} + + property width: Integer read GetWidth; + property height: Integer read GetHeight; + property count: Integer read GetCount; + property backAnim: Boolean read mBackanim; (* this property must be located at TAnimState? *) + end; + + procedure r_Textures_Initialize; + procedure r_Textures_Finalize; + + function r_Textures_LoadFromFile (const filename: AnsiString; log: Boolean = True): TGLTexture; + function r_Textures_LoadMultiFromFile (const filename: AnsiString; log: Boolean = True): TGLMultiTexture; + function r_Textures_LoadMultiFromFileAndInfo (const filename: AnsiString; w, h, count: Integer; backanim: Boolean; log: Boolean = True): TGLMultiTexture; + +implementation + + uses + SysUtils, Classes, + e_log, e_res, WADReader, Config, + Imaging, ImagingTypes, ImagingUtility + ; + + var + maxTileSize: Integer; + atl: array of TGLAtlas; +// tex: array of TGLTexture; + + (* --------- TGLAtlasNode --------- *) + + constructor TGLAtlasNode.Create (base: TGLAtlas); + begin + ASSERT(base <> nil); + inherited Create(); + self.mBase := base; + end; + + destructor TGLAtlasNode.Destroy; + begin + inherited; + end; + + function TGLAtlasNode.GetID (): GLuint; + begin + result := self.base.id + end; + + procedure r_Textures_UpdateNode (n: TGLAtlasNode; data: Pointer; x, y, w, h: Integer); + begin + ASSERT(n <> nil); + // ASSERT(n.leaf); + ASSERT(n.base <> nil); + ASSERT(data <> nil); + ASSERT(x >= 0); + ASSERT(y >= 0); + ASSERT(n.l + x + w - 1 <= n.r); + ASSERT(n.t + y + h - 1 <= n.b); + ASSERT(n.id > 0); + glBindTexture(GL_TEXTURE_2D, n.id); + glTexSubImage2D(GL_TEXTURE_2D, 0, n.l + x, n.t + y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, data); + glBindTexture(GL_TEXTURE_2D, 0); + end; + + (* --------- TGLAtlas --------- *) + + constructor TGLAtlas.Create (ww, hh: Integer; id: GLuint); + begin + ASSERT(ww > 0); + ASSERT(hh > 0); + inherited Create(ww, hh); + self.mID := id; + end; + + destructor TGLAtlas.Destroy; + begin + inherited; + end; + + function TGLAtlas.CreateNode (): TGLAtlasNode; + begin + result := TGLAtlasNode.Create(self); + end; + + function TGLAtlas.Alloc (ww, hh: Integer): TGLAtlasNode; + begin + result := TGLAtlasNode(inherited Alloc(ww, hh)); + end; + + function r_Textures_AllocHWTexture (w, h: Integer): GLuint; + var id: GLuint; + begin + glGenTextures(1, @id); + if id <> 0 then + begin + glBindTexture(GL_TEXTURE_2D, id); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, nil); + glBindTexture(GL_TEXTURE_2D, 0); + end; + result := id + end; + + function r_Textures_AllocAtlas (): TGLAtlas; + var i: Integer; id: GLuint; + begin + result := nil; + id := r_Textures_AllocHWTexture(maxTileSize, maxTileSize); + if id <> 0 then + begin + i := Length(atl); + SetLength(atl, i + 1); + atl[i] := TGLAtlas.Create(maxTileSize, maxTileSize, id); + result := atl[i]; + end; + end; + + function r_Textures_AllocNode (w, h: Integer): TGLAtlasNode; + var i: Integer; n: TGLAtlasNode; a: TGLAtlas; + begin + n := nil; + if atl <> nil then + begin + i := High(atl); + while (i >= 0) and (n = nil) do + begin + n := atl[i].Alloc(w, h); + Dec(i); + end; + end; + if n = nil then + begin + a := r_Textures_AllocAtlas(); + if a <> nil then + n := a.Alloc(w, h); + end; + result := n + end; + + (* --------- TGLTexture --------- *) + + destructor TGLTexture.Destroy; + var i: Integer; + begin + if self.mTile <> nil then + begin + for i := 0 to High(self.mTile) do + begin + if self.mTile[i] <> nil then + begin + self.mTile[i].Dealloc; + self.mTile[i] := nil; + end; + end; + self.mTile := nil; + end; + inherited; + end; + + function TGLTexture.GetLines (): Integer; + begin + ASSERT(self.mTile <> nil); + result := Length(self.mTile) div self.mCols + end; + + function TGLTexture.GetTile (col, line: Integer): TGLAtlasNode; + var i: Integer; + begin + ASSERT(col >= 0); + ASSERT(col <= mCols); + ASSERT(self.mTile <> nil); + i := line * mCols + col; + ASSERT(i >= 0); + ASSERT(i < Length(mTile)); + result := mTile[i]; + ASSERT(result <> nil) + end; + + function r_Textures_Alloc (w, h: Integer): TGLTexture; + var x, y, mw, mh, cols, lines: Integer; t: TGLTexture; + begin + ASSERT(w > 0); + ASSERT(h > 0); + cols := (w + maxTileSize - 1) div maxTileSize; + lines := (h + maxTileSize - 1) div maxTileSize; + t := TGLTexture.Create; + t.mWidth := w; + t.mHeight := h; + t.mCols := cols; + // t.mLines := lines; + SetLength(t.mTile, cols * lines); + for y := 0 to lines - 1 do + begin + mh := Min(maxTileSize, h - y * maxTileSize); + ASSERT(mh > 0); + for x := 0 to cols - 1 do + begin + mw := Min(maxTileSize, w - x * maxTileSize); + ASSERT(mw > 0); + t.mTile[y * cols + x] := r_Textures_AllocNode(mw, mh); + end + end; + result := t; + end; + + (* --------- TGLMultiTexture --------- *) + + destructor TGLMultiTexture.Destroy; + var i: Integer; + begin + for i := 0 to self.count - 1 do + self.mTexture[i].Free; + self.mTexture := nil; + inherited; + end; + + function TGLMultiTexture.GetWidth (): Integer; + begin + result := self.mTexture[0].width + end; + + function TGLMultiTexture.GetHeight (): Integer; + begin + result := self.mTexture[0].height + end; + + function TGLMultiTexture.GetCount (): Integer; + begin + result := Length(self.mTexture) + end; + + function TGLMultiTexture.GetTexture (i: Integer): TGLTexture; + begin + ASSERT(i >= 0); + ASSERT(i < self.count); + result := self.mTexture[i]; + ASSERT(result <> nil); + end; + + (* --------- Init / Fin --------- *) + + function r_Textures_GetMaxHardwareSize (): Integer; + var size: GLint = 0; + begin + glGetIntegerv(GL_MAX_TEXTURE_SIZE, @size); + if size < 64 then size := 64; + //if size > 512 then size := 512; + //size := 64; // !!! + result := size; + end; + + procedure r_Textures_Initialize; + begin + maxTileSize := r_Textures_GetMaxHardwareSize(); + end; + + procedure r_Textures_Finalize; + var i: Integer; + begin + if atl <> nil then + begin + for i := 0 to High(atl) do + begin + glDeleteTextures(1, @atl[i].id); + atl[i].id := 0; + atl[i].Free; + end; + atl := nil; + end; + end; + + function r_Textures_LoadFromImage (var img: TImageData): TGLTexture; + var t: TGLTexture; n: TGLAtlasNode; c: TDynImageDataArray; cw, ch, i, j: LongInt; + begin + // e_logwritefln('r_Textures_CreateFromImage: w=%s h=%s', [img.width, img.height]); + result := nil; + if SplitImage(img, c, maxTileSize, maxTileSize, cw, ch, False) then + begin + t := r_Textures_Alloc(img.width, img.height); + if t <> nil then + begin + ASSERT(cw = t.cols); + ASSERT(ch = t.lines); + for j := 0 to ch - 1 do + begin + for i := 0 to cw - 1 do + begin + n := t.GetTile(i, j); + if n <> nil then + r_Textures_UpdateNode(n, c[j * cw + i].bits, 0, 0, n.width, n.height) + end + end; + result := t + end; + FreeImagesInArray(c); + end; + end; + + function r_Textures_LoadFromMemory (data: Pointer; size: LongInt): TGLTexture; + var img: TImageData; + begin + result := nil; + if (data <> nil) and (size > 0) then + begin + InitImage(img); + try + if LoadImageFromMemory(data, size, img) then + if ConvertImage(img, TImageFormat.ifA8R8G8B8) then + if SwapChannels(img, ChannelRed, ChannelBlue) then // wth + result := r_Textures_LoadFromImage(img) + except + end; + FreeImage(img); + end; + end; + + function r_Textures_LoadFromFile (const filename: AnsiString; log: Boolean = True): TGLTexture; + var wad: TWADFile; wadName, resName: AnsiString; data: Pointer; size: Integer; + begin + result := nil; + wadName := g_ExtractWadName(filename); + wad := TWADFile.Create(); + if wad.ReadFile(wadName) then + begin + resName := g_ExtractFilePathName(filename); + if wad.GetResource(resName, data, size, log) then + begin + result := r_Textures_LoadFromMemory(data, size); + FreeMem(data); + end; + wad.Free + end + end; + + function r_Textures_LoadMultiFromImageAndInfo (var img: TImageData; w, h, c: Integer; b: Boolean): TGLMultiTexture; + var t: TImageData; a: array of TGLTexture; i: Integer; m: TGLMultiTexture; + begin + ASSERT(w >= 0); + ASSERT(h >= 0); + ASSERT(c >= 1); + result := nil; + SetLength(a, c); + for i := 0 to c - 1 do + begin + InitImage(t); + if NewImage(w, h, img.Format, t) then + if CopyRect(img, w * i, 0, w, h, t, 0, 0) then + a[i] := r_Textures_LoadFromImage(t); + ASSERT(a[i] <> nil); + FreeImage(t); + end; + m := TGLMultiTexture.Create(); + m.mTexture := a; + m.mBackanim := b; + ASSERT(m.mTexture <> nil); + result := m; + end; + + function r_Textures_LoadMultiFromDataAndInfo (data: Pointer; size: LongInt; w, h, c: Integer; b: Boolean): TGLMultiTexture; + var img: TImageData; + begin + ASSERT(w > 0); + ASSERT(h > 0); + ASSERT(c >= 1); + result := nil; + if (data <> nil) and (size > 0) then + begin + InitImage(img); + try + if LoadImageFromMemory(data, size, img) then + if ConvertImage(img, TImageFormat.ifA8R8G8B8) then + if SwapChannels(img, ChannelRed, ChannelBlue) then // wtf + result := r_Textures_LoadMultiFromImageAndInfo(img, w, h, c, b) + except + end; + FreeImage(img); + end; + end; + + function r_Textures_LoadMultiFromWad (wad: TWADFile): TGLMultiTexture; + var data: Pointer; size: LongInt; TexRes: AnsiString; w, h, c: Integer; b: Boolean; cfg: TConfig; img: TImageData; + begin + ASSERT(wad <> nil); + result := nil; + if wad.GetResource('TEXT/ANIM', data, size) then + begin + cfg := TConfig.CreateMem(data, size); + FreeMem(data); + if cfg <> nil then + begin + TexRes := cfg.ReadStr('', 'resource', ''); + w := cfg.ReadInt('', 'framewidth', 0); + h := cfg.ReadInt('', 'frameheight', 0); + c := cfg.ReadInt('', 'framecount', 0); + b := cfg.ReadBool('', 'backanim', false); + if (TexRes <> '') and (w > 0) and (h > 0) and (c > 0) then + begin + if wad.GetResource('TEXTURES/' + TexRes, data, size) then + begin + InitImage(img); + try + if LoadImageFromMemory(data, size, img) then + if ConvertImage(img, TImageFormat.ifA8R8G8B8) then + if SwapChannels(img, ChannelRed, ChannelBlue) then // wtf + result := r_Textures_LoadMultiFromImageAndInfo(img, w, h, c, b) + finally + FreeMem(data); + end; + FreeImage(img); + end + end; + cfg.Free; + end + end; + end; + + function r_Textures_LoadMultiFromMemory (data: Pointer; size: LongInt): TGLMultiTexture; + var wad: TWADFile; t: TGLTexture; m: TGLMultiTexture; + begin + result := nil; + if (data <> nil) and (size > 0) then + begin + t := r_Textures_LoadFromMemory(data, size); + if t <> nil then + begin + m := TGLMultiTexture.Create(); + SetLength(m.mTexture, 1); + m.mTexture[0] := t; + m.mBackanim := false; + result := m; + end + else if IsWadData(data, size) then + begin + wad := TWADFile.Create(); + if wad.ReadMemory(data, size) then + begin + result := r_Textures_LoadMultiFromWad(wad); + wad.Free; + end + end + end + end; + + function r_Textures_LoadMultiFromFile (const filename: AnsiString; log: Boolean = True): TGLMultiTexture; + var wad: TWADFile; wadName, resName: AnsiString; data: Pointer; size: Integer; t: TGLTexture; + begin + result := nil; + wadName := g_ExtractWadName(filename); + wad := TWADFile.Create(); + if wad.ReadFile(wadName) then + begin + resName := g_ExtractFilePathName(filename); + if wad.GetResource(resName, data, size, log) then + begin + result := r_Textures_LoadMultiFromMemory(data, size); + FreeMem(data); + end; + wad.Free + end + end; + + function r_Textures_LoadMultiFromFileAndInfo (const filename: AnsiString; w, h, count: Integer; backanim: Boolean; log: Boolean = True): TGLMultiTexture; + var wad: TWADFile; wadName, resName: AnsiString; data: Pointer; size: Integer; + begin + ASSERT(w > 0); + ASSERT(h > 0); + ASSERT(count >= 1); + result := nil; + wadName := g_ExtractWadName(filename); + wad := TWADFile.Create(); + if wad.ReadFile(wadName) then + begin + resName := g_ExtractFilePathName(filename); + if wad.GetResource(resName, data, size, log) then + begin + result := r_Textures_LoadMultiFromDataAndInfo(data, size, w, h, count, backanim); + FreeMem(data); + end; + wad.Free + end + end; + +end. diff --git a/src/game/sdl2/g_system.pas b/src/game/sdl2/g_system.pas index daec5d9..e2e7132 100644 --- a/src/game/sdl2/g_system.pas +++ b/src/game/sdl2/g_system.pas @@ -19,12 +19,26 @@ interface uses Utils; + type + TGLProfile = (Core, Compat, Common, CommonLite); + + type + TGLDisplayInfo = record + w, h, bpp: Integer; + fullscreen: Boolean; + maximized: Boolean; + major, minor: Integer; + profile: TGLProfile; + end; + (* --- Graphics --- *) function sys_GetDisplayModes (bpp: Integer): SSArray; function sys_SetDisplayMode (w, h, bpp: Integer; fullscreen, maximized: Boolean): Boolean; procedure sys_EnableVSync (yes: Boolean); procedure sys_Repaint; + function sys_SetDisplayModeGL (const info: TGLDisplayInfo): Boolean; + (* --- Input --- *) function sys_HandleInput (): Boolean; procedure sys_RequestQuit; @@ -70,6 +84,7 @@ implementation JoystickHandle: array [0..e_MaxJoys - 1] of PSDL_Joystick; JoystickHatState: array [0..e_MaxJoys - 1, 0..e_MaxJoyHats - 1, HAT_LEFT..HAT_DOWN] of Boolean; JoystickZeroAxes: array [0..e_MaxJoys - 1, 0..e_MaxJoyAxes - 1] of Integer; + rMajor, rMinor, rProfile: Integer; {$IFDEF ENABLE_TOUCH} var (* touch *) @@ -96,20 +111,18 @@ implementation result := false; if window = nil then begin - {$IFDEF USE_GLES1} - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 1); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); - {$ELSE} - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, rMajor); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, rMinor); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, rProfile); + if rProfile in [SDL_GL_CONTEXT_PROFILE_CORE, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY] then + begin SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16); SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); // lights; it is enough to have 1-bit stencil buffer for lighting, but... - {$ENDIF} + end; flags := SDL_WINDOW_OPENGL or SDL_WINDOW_RESIZABLE; if fullScreen then flags := flags or SDL_WINDOW_FULLSCREEN; if maximized then flags := flags or SDL_WINDOW_MAXIMIZED; @@ -234,9 +247,31 @@ implementation function sys_SetDisplayMode (w, h, bpp: Integer; fullScreen, maximized: Boolean): Boolean; begin + {$IFDEF USE_GLES1} + rMajor := 1; + rMinor := 1; + rProfile := SDL_GL_CONTEXT_PROFILE_ES; + {$ELSE} + rMajor := 2; + rMinor := 1; + rProfile := SDL_GL_CONTEXT_PROFILE_COMPATIBILITY; + {$ENDIF} result := InitWindow(w, h, bpp, fullScreen, maximized) end; + function sys_SetDisplayModeGL (const info: TGLDisplayInfo): Boolean; + begin + rMajor := info.major; + rMinor := info.minor; + case info.profile of + TGLProfile.Core: rProfile := SDL_GL_CONTEXT_PROFILE_CORE; + TGLProfile.Compat: rProfile := SDL_GL_CONTEXT_PROFILE_COMPATIBILITY; + TGLProfile.Common: rProfile := SDL_GL_CONTEXT_PROFILE_ES; + TGLProfile.CommonLite: rProfile := SDL_GL_CONTEXT_PROFILE_ES; + end; + result := InitWindow(info.w, info.h, info.bpp, info.fullscreen, info.maximized); + end; + (* --------- Joystick --------- *) procedure HandleJoyButton (var ev: TSDL_JoyButtonEvent); diff --git a/src/shared/a_modes.inc b/src/shared/a_modes.inc index 058f2b9..9f7caa4 100644 --- a/src/shared/a_modes.inc +++ b/src/shared/a_modes.inc @@ -277,19 +277,23 @@ {$ENDIF} {$IF DEFINED(USE_GLSTUB)} - {$IF DEFINED(USE_GLES1) OR DEFINED(USE_OPENGL) OR DEFINED(USE_STUBRENDER)} + {$IF DEFINED(USE_GLES1) OR DEFINED(USE_OPENGL) OR DEFINED(USE_STUBRENDER) OR DEFINED(USE_NEWGL)} {$ERROR Only one render driver must be selected!} {$ENDIF} {$ELSEIF DEFINED(USE_GLES1)} - {$IF DEFINED(USE_GLSTUB) OR DEFINED(USE_OPENGL) OR DEFINED(USE_STUBRENDER)} + {$IF DEFINED(USE_GLSTUB) OR DEFINED(USE_OPENGL) OR DEFINED(USE_STUBRENDER) OR DEFINED(USE_NEWGL)} {$ERROR Only one render driver must be selected!} {$ENDIF} {$ELSEIF DEFINED(USE_OPENGL)} - {$IF DEFINED(USE_GLSTUB) OR DEFINED(USE_GLES1) OR DEFINED(USE_STUBRENDER)} + {$IF DEFINED(USE_GLSTUB) OR DEFINED(USE_GLES1) OR DEFINED(USE_STUBRENDER) OR DEFINED(USE_NEWGL)} {$ERROR Only one render driver must be selected!} {$ENDIF} {$ELSEIF DEFINED(USE_STUBRENDER)} - {$IF DEFINED(USE_GLSTUB) OR DEFINED(USE_OPENGL) OR DEFINED(USE_GLES1)} + {$IF DEFINED(USE_GLSTUB) OR DEFINED(USE_OPENGL) OR DEFINED(USE_GLES1) OR DEFINED(USE_NEWGL)} + {$ERROR Only one render driver must be selected!} + {$ENDIF} +{$ELSEIF DEFINED(USE_NEWGL)} + {$IF DEFINED(USE_GLSTUB) OR DEFINED(USE_OPENGL) OR DEFINED(USE_GLES1) OR DEFINED(USE_STUBRENDER)} {$ERROR Only one render driver must be selected!} {$ENDIF} {$ELSEIF DEFINED(ENABLE_RENDER)} -- 2.29.2