From e452d337f0620b50d8d978b534437b2732134d8f Mon Sep 17 00:00:00 2001 From: fgsfds Date: Mon, 10 Feb 2020 00:07:58 +0300 Subject: [PATCH] added optional framebuffer and resolution scaling --- src/engine/e_graphics.pas | 68 ++++++++++++++++++++++++++++++++++++++ src/game/g_game.pas | 12 ++++--- src/game/g_holmes.pas | 28 ++++++++-------- src/game/g_menu.pas | 37 +++++++++++++++++---- src/game/g_options.pas | 33 ++++++++++-------- src/game/g_window.pas | 10 ++++++ src/game/sdl/g_system.pas | 22 ++++++++++-- src/game/sdl2/g_system.pas | 22 ++++++++++-- src/game/sdl2/g_touch.pas | 6 ++-- 9 files changed, 192 insertions(+), 46 deletions(-) diff --git a/src/engine/e_graphics.pas b/src/engine/e_graphics.pas index 6e8966e..18f75c4 100644 --- a/src/engine/e_graphics.pas +++ b/src/engine/e_graphics.pas @@ -62,6 +62,8 @@ type procedure e_InitGL(); procedure e_SetViewPort(X, Y, Width, Height: Word); procedure e_ResizeWindow(Width, Height: Integer); +procedure e_ResizeFramebuffer(Width, Height: Integer); +procedure e_BlitFramebuffer(WinWidth, WinHeight: Integer); procedure e_Draw(ID: DWORD; X, Y: Integer; Alpha: Byte; AlphaChannel: Boolean; Blending: Boolean; Mirror: TMirrorType = TMirrorType.None); @@ -151,6 +153,7 @@ var e_NoGraphics: Boolean = False; e_FastScreenshots: Boolean = true; // it's REALLY SLOW with `false` g_dbg_scale: Single = 1.0; + r_pixel_scale: Single = 1.0; implementation @@ -196,6 +199,11 @@ var e_TextureFonts: array of TTextureFont = nil; e_CharFonts: array of TCharFont; //e_SavedTextures: array of TSavedTexture; + e_FBO: GLuint = 0; + e_RBO: GLuint = 0; + e_Frame: GLuint = 0; + e_FrameW: Integer = -1; + e_FrameH: Integer = -1; //function e_getTextGLId (ID: DWORD): GLuint; begin result := e_Textures[ID].tx.id; end; @@ -371,6 +379,50 @@ begin if Height <> nil then Height^ := e_Textures[ID].tx.Height; end; +procedure e_ResizeFramebuffer(Width, Height: Integer); +begin + glBindTexture(GL_TEXTURE_2D, 0); + glBindRenderbuffer(GL_RENDERBUFFER, 0); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + if e_Frame > 0 then + begin + glDeleteTextures(1, @e_Frame); + e_Frame := 0; + end; + + if e_RBO > 0 then + begin + glDeleteRenderbuffers(1, @e_RBO); + e_RBO := 0; + end; + + if e_FBO > 0 then + begin + glDeleteFramebuffers(1, @e_FBO); + e_FBO := 0; + end; + + e_FrameW := Width; + e_FrameH := Height; + + glGenFramebuffers(1, @e_FBO); + + glGenTextures(1, @e_Frame); + glBindTexture(GL_TEXTURE_2D, e_Frame); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, Width, Height, 0, GL_RGB, GL_UNSIGNED_BYTE, nil); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + glGenRenderbuffers(1, @e_RBO); + glBindRenderbuffer(GL_RENDERBUFFER, e_RBO); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, Width, Height); + + glBindFramebuffer(GL_FRAMEBUFFER, e_FBO); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, e_Frame, 0); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, e_RBO); +end; + procedure e_ResizeWindow(Width, Height: Integer); begin if Height = 0 then @@ -399,6 +451,22 @@ begin glTexCoord2f(u, v); glVertex2i(x1, y0); end; +procedure e_BlitFramebuffer(WinWidth, WinHeight: Integer); +begin + if (e_FBO = 0) or (e_Frame = 0) then exit; + glDisable(GL_BLEND); + glEnable(GL_TEXTURE_2D); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glBindTexture(GL_TEXTURE_2D, e_Frame); + glColor4ub(255, 255, 255, 255); + e_SetViewPort(0, 0, WinWidth, WinHeight); + glBegin(GL_QUADS); + drawTxQuad(0, 0, WinWidth, WinHeight, e_FrameW, e_FrameH, 1, 1, TMirrorType.None); + glEnd(); + glBindFramebuffer(GL_FRAMEBUFFER, e_FBO); + e_SetViewPort(0, 0, e_FrameW, e_FrameH); +end; + procedure e_Draw(ID: DWORD; X, Y: Integer; Alpha: Byte; AlphaChannel: Boolean; Blending: Boolean; Mirror: TMirrorType = TMirrorType.None); begin diff --git a/src/game/g_game.pas b/src/game/g_game.pas index 3dc360c..feaf159 100644 --- a/src/game/g_game.pas +++ b/src/game/g_game.pas @@ -291,7 +291,8 @@ var gMapToDelete: String; gTempDelete: Boolean = False; gLastMap: Boolean = False; - gWinSizeX, gWinSizeY: Integer; + gScreenWidth: Word; + gScreenHeight: Word; gResolutionChange: Boolean = False; gRC_Width, gRC_Height: Integer; gRC_FullScreen, gRC_Maximized: Boolean; @@ -1065,7 +1066,7 @@ begin SortGameStat(CustomStat.PlayerStat); - if (gSaveStats or gScreenshotStats) and (Length(gPlayers) > 1) then + if (gSaveStats or gScreenshotStats) and (Length(CustomStat.PlayerStat) > 1) then begin t := Now; if g_Game_IsNet then StatFilename := NetServerName else StatFilename := 'local'; @@ -2876,7 +2877,7 @@ begin end; // HACK: take stats screenshot immediately after the first frame of the stats showing - if gScreenshotStats and not StatShotDone then + if gScreenshotStats and (not StatShotDone) and (Length(CustomStat.PlayerStat) > 1) then begin g_TakeScreenShot('stats/' + StatFilename); StatShotDone := True; @@ -3418,7 +3419,7 @@ begin end else begin - glScissor(0, 0, gWinSizeX, gWinSizeY); + glScissor(0, 0, gScreenWidth, gScreenHeight); end; // no need to clear stencil buffer, light blitting will do it for us... but only for normal scale if (g_dbg_scale <> 1.0) then glClear(GL_STENCIL_BUFFER_BIT); @@ -5973,7 +5974,7 @@ begin if cmd = 'd_window' then begin g_Console_Add(Format('gScreenWidth = %d, gScreenHeight = %d', [gScreenWidth, gScreenHeight])); - g_Console_Add(Format('gWinSizeX = %d, gWinSizeY = %d', [gWinSizeX, gWinSizeY])); + g_Console_Add(Format('gScreenWidth = %d, gScreenHeight = %d', [gScreenWidth, gScreenHeight])); end else if cmd = 'd_sounds' then begin @@ -8182,6 +8183,7 @@ begin conRegVar('dbg_ignore_level_bounds', @g_dbg_ignore_bounds, 'ignore level bounds', '', false); conRegVar('r_scale', @g_dbg_scale, 0.01, 100.0, 'render scale', '', false); + conRegVar('r_resolution_scale', @r_pixel_scale, 0.01, 100.0, 'upscale factor', '', false); conRegVar('light_enabled', @gwin_k8_enable_light_experiments, 'enable/disable dynamic lighting', 'lighting'); conRegVar('light_player_halo', @g_playerLight, 'enable/disable player halo', 'player light halo'); diff --git a/src/game/g_holmes.pas b/src/game/g_holmes.pas index a2a55ad..c173bd0 100644 --- a/src/game/g_holmes.pas +++ b/src/game/g_holmes.pas @@ -559,7 +559,7 @@ var procedure clearEdgeBmp (); begin - SetLength(edgeBmp, (gWinSizeX+4)*(gWinSizeY+4)); + SetLength(edgeBmp, (gScreenWidth+4)*(gScreenHeight+4)); FillChar(edgeBmp[0], Length(edgeBmp)*sizeof(edgeBmp[0]), 0); end; @@ -574,19 +574,19 @@ var begin sx := pan.X-(vpx-1); len := pan.Width; - if (len > gWinSizeX+4) then len := gWinSizeX+4; + if (len > gScreenWidth+4) then len := gScreenWidth+4; if (sx < 0) then begin len += sx; sx := 0; end; - if (sx+len > gWinSizeX+4) then len := gWinSizeX+4-sx; + if (sx+len > gScreenWidth+4) then len := gScreenWidth+4-sx; if (len < 1) then exit; assert(sx >= 0); - assert(sx+len <= gWinSizeX+4); + assert(sx+len <= gScreenWidth+4); y0 := pan.Y-(vpy-1); y1 := y0+pan.Height; if (y0 < 0) then y0 := 0; - if (y1 > gWinSizeY+4) then y1 := gWinSizeY+4; + if (y1 > gScreenHeight+4) then y1 := gScreenHeight+4; while (y0 < y1) do begin - FillChar(edgeBmp[y0*(gWinSizeX+4)+sx], len*sizeof(edgeBmp[0]), 1); + FillChar(edgeBmp[y0*(gScreenWidth+4)+sx], len*sizeof(edgeBmp[0]), 1); Inc(y0); end; end @@ -651,15 +651,15 @@ var glColor4f(r/255.0, g/255.0, b/255.0, 1.0); for y := 1 to vph do begin - a := @edgeBmp[y*(gWinSizeX+4)+1]; + a := @edgeBmp[y*(gScreenWidth+4)+1]; startLine(y); for x := 1 to vpw do begin if (a[0] <> 0) then begin - if (a[-1] = 0) or (a[1] = 0) or (a[-(gWinSizeX+4)] = 0) or (a[gWinSizeX+4] = 0) or - (a[-(gWinSizeX+4)-1] = 0) or (a[-(gWinSizeX+4)+1] = 0) or - (a[gWinSizeX+4-1] = 0) or (a[gWinSizeX+4+1] = 0) then + if (a[-1] = 0) or (a[1] = 0) or (a[-(gScreenWidth+4)] = 0) or (a[gScreenWidth+4] = 0) or + (a[-(gScreenWidth+4)-1] = 0) or (a[-(gScreenWidth+4)+1] = 0) or + (a[gScreenWidth+4-1] = 0) or (a[gScreenWidth+4+1] = 0) then begin putPixel(x); end; @@ -684,7 +684,7 @@ var glColor4f(r/255.0, g/255.0, b/255.0, 1.0); for y := 1 to vph do begin - a := @edgeBmp[y*(gWinSizeX+4)+1]; + a := @edgeBmp[y*(gScreenWidth+4)+1]; startLine(y); for x := 1 to vpw do begin @@ -1258,7 +1258,7 @@ begin gxSetContext(hlmContext); try - //glScissor(0, gWinSizeY-gPlayerScreenSize.Y-1, vpw, vph); + //glScissor(0, gScreenHeight-gPlayerScreenSize.Y-1, vpw, vph); //hlmContext.clip := TGxRect.Create(0, gScreenHeight-gPlayerScreenSize.Y-1, gPlayerScreenSize.X, gPlayerScreenSize.Y); { @@ -1333,11 +1333,11 @@ begin begin for dx := -1 to 1 do begin - if (dx <> 0) or (dy <> 0) then hlmContext.drawText(4+dx, gWinSizeY-10+dy, s); + if (dx <> 0) or (dy <> 0) then hlmContext.drawText(4+dx, gScreenHeight-10+dy, s); end; end; hlmContext.color := TGxRGBA.Create(255, 255, 0); - hlmContext.drawText(4, gWinSizeY-10, s); + hlmContext.drawText(4, gScreenHeight-10, s); gxSetContext(nil); end; end; diff --git a/src/game/g_menu.pas b/src/game/g_menu.pas index 3513439..fe65078 100644 --- a/src/game/g_menu.pas +++ b/src/game/g_menu.pas @@ -38,6 +38,7 @@ var PromptIP: string; PromptPort: Word; TempScale: Integer = -1; + TempResScale: Integer = -1; implementation @@ -1881,8 +1882,8 @@ begin menu := TGUIMenu(g_GUI_GetWindow('OptionsVideoResMenu').GetControl('mOptionsVideoResMenu')); TGUILabel(menu.GetControl('lbCurrentRes')).Text := - IntToStr(gScreenWidth) + - ' x ' + IntToStr(gScreenHeight) + + IntToStr(gWinSizeX) + + ' x ' + IntToStr(gWinSizeY) + ', ' + IntToStr(gBPP) + ' bpp'; with TGUIListBox(menu.GetControl('lsResolution')) do @@ -1904,6 +1905,9 @@ begin ItemIndex := 0 else ItemIndex := 1; + + TempResScale := Round(r_pixel_scale - 1); + TGUIScroll(menu.GetControl('scResFactor')).Value := TempResScale; end; procedure ProcApplyVideoOptions(); @@ -1911,18 +1915,34 @@ var menu: TGUIMenu; Fullscreen: Boolean; SWidth, SHeight: Integer; + ScaleChanged: Boolean; str: String; begin menu := TGUIMenu(g_GUI_GetWindow('OptionsVideoResMenu').GetControl('mOptionsVideoResMenu')); str := TGUIListBox(menu.GetControl('lsResolution')).SelectedItem; - SScanf(str, '%dx%d', [@SWidth, @SHeight]); + if str <> '' then + SScanf(str, '%dx%d', [@SWidth, @SHeight]) + else + begin + SWidth := gWinSizeX; + SHeight := gWinSizeY; + end; Fullscreen := TGUISwitch(menu.GetControl('swFullScreen')).ItemIndex = 0; - if (SWidth <> gScreenWidth) or - (SHeight <> gScreenHeight) or - (Fullscreen <> gFullscreen) then + ScaleChanged := False; + if TGUIScroll(menu.GetControl('scResFactor')).Value <> TempResScale then + begin + TempResScale := TGUIScroll(menu.GetControl('scResFactor')).Value; + r_pixel_scale := TempResScale + 1; + ScaleChanged := True; + end; + + if (SWidth <> gWinSizeX) or + (SHeight <> gWinSizeY) or + (Fullscreen <> gFullscreen) or + ScaleChanged then begin gResolutionChange := True; gRC_Width := SWidth; @@ -2715,6 +2735,11 @@ begin AddItem(_lc[I_MENU_YES]); AddItem(_lc[I_MENU_NO]); end; + with AddScroll(_lc[I_MENU_GAME_SCALE_FACTOR]) do + begin + Name := 'scResFactor'; + Max := 10; + end; AddSpace(); AddButton(@ProcApplyVideoOptions, _lc[I_MENU_RESOLUTION_APPLY]); UpdateIndex(); diff --git a/src/game/g_options.pas b/src/game/g_options.pas index 0288931..706ca33 100644 --- a/src/game/g_options.pas +++ b/src/game/g_options.pas @@ -35,14 +35,14 @@ procedure g_Options_Commands (p: SSArray); const DF_Default_Megawad_Start = 'megawads/DOOM2D.WAD:\MAP01'; var - gScreenWidth: Word; - gScreenHeight: Word; gBPP: Integer; gFreq: Byte; gFullscreen: Boolean; + gWinSizeX, gWinSizeY: Integer; gWinMaximized: Boolean; gVSync: Boolean; glLegacyNPOT: Boolean; + glRenderToFBO: Boolean = True; gTextureFilter: Boolean; gNoSound: Boolean; gSoundLevel: Integer; @@ -132,6 +132,8 @@ begin (* Display 0 = Primary display *) gScreenWidth := 640; gScreenHeight := 480; + gWinSizeX := 640; + gWinSizeY := 480; //gBPP := SDL_BITSPERPIXEL(dispaly.format); gBPP := 32; {$IFDEF ANDROID} @@ -142,8 +144,8 @@ begin if SDL_GetDesktopDisplayMode(0, @display) = 0 then begin {$IFDEF ANDROID} - gScreenWidth := display.w; - gScreenHeight := display.h; + gWinSizeX := display.w; + gWinSizeY := display.h; {$ELSE} (* Window must be smaller than display *) closest.w := display.w; @@ -165,8 +167,8 @@ begin SDL_GetClosestDisplayMode(0, @target, @closest); Dec(percentage); end; - gScreenWidth := closest.w; - gScreenHeight := closest.h; + gWinSizeX := closest.w; + gWinSizeY := closest.h; //gBPP := SDL_BITSPERPIXEL(closest.format); (* Resolution list didn't work for some reason *) {$ENDIF} end @@ -179,29 +181,31 @@ begin gVSync := True; gTextureFilter := True; glLegacyNPOT := False; - gRC_Width := gScreenWidth; - gRC_Height := gScreenHeight; + gRC_Width := gWinSizeX; + gRC_Height := gWinSizeY; gRC_FullScreen := gFullScreen; gRC_Maximized := gWinMaximized; - e_LogWriteLn('g_Options_SetDefaultVideo: w = ' + IntToStr(gScreenWidth) + ' h = ' + IntToStr(gScreenHeight)); + e_LogWriteLn('g_Options_SetDefaultVideo: w = ' + IntToStr(gWinSizeX) + ' h = ' + IntToStr(gWinSizeY)); g_Console_ResetBinds; end; {$ELSE} procedure g_Options_SetDefaultVideo; begin - gScreenWidth := 640; - gScreenHeight := 480; + gWinSizeX := 640; + gWinSizeY := 480; gBPP := 32; gFullScreen := False; gWinMaximized := False; gVSync := True; gTextureFilter := True; glLegacyNPOT := False; - gRC_Width := gScreenWidth; - gRC_Height := gScreenHeight; + gScreenWidth := gWinSizeX; + gScreenHeight := gWinSizeY; + gRC_Width := gWinSizeX; + gRC_Height := gWinSizeY; gRC_FullScreen := gFullScreen; gRC_Maximized := gWinMaximized; - e_LogWriteLn('g_Options_SetDefaultVideo: w = ' + IntToStr(gScreenWidth) + ' h = ' + IntToStr(gScreenHeight)); + e_LogWriteLn('g_Options_SetDefaultVideo: w = ' + IntToStr(gWinSizeX) + ' h = ' + IntToStr(gWinSizeY)); g_Console_ResetBinds; end; {$ENDIF} @@ -811,6 +815,7 @@ initialization conRegVar('r_vsync', @gVSync, '', ''); conRegVar('r_texfilter', @gTextureFilter, '', ''); conRegVar('r_npot', @glNPOTOverride, '', ''); + conRegVar('r_fbo', @glRenderToFBO, '', ''); (* Sound *) conRegVar('s_nosound', @gNoSound, '', ''); diff --git a/src/game/g_window.pas b/src/game/g_window.pas index afc23e2..7774c0a 100644 --- a/src/game/g_window.pas +++ b/src/game/g_window.pas @@ -326,6 +326,15 @@ begin mdfo.Free(); Halt(0); end; + + if (arg = '--pixel-scale') or (arg = '-pixel-scale') then + begin + if (idx <= ParamCount) then + begin + if not conParseFloat(r_pixel_scale, ParamStr(idx)) then r_pixel_scale := 1.0; + Inc(idx); + end; + end; end; {$IFNDEF USE_SYSSTUB} @@ -333,6 +342,7 @@ begin glLegacyNPOT := not (GLExtensionSupported('GL_ARB_texture_non_power_of_two') or GLExtensionSupported('GL_OES_texture_npot')); {$ELSE} glLegacyNPOT := False; + glRenderToFBO := False; {$ENDIF} if glNPOTOverride and glLegacyNPOT then begin diff --git a/src/game/sdl/g_system.pas b/src/game/sdl/g_system.pas index 0077089..e5f6275 100644 --- a/src/game/sdl/g_system.pas +++ b/src/game/sdl/g_system.pas @@ -74,10 +74,17 @@ implementation begin gWinSizeX := w; gWinSizeY := h; - gScreenWidth := w; - gScreenHeight := h; gRC_Width := w; gRC_Height := h; + if glRenderToFBO then + begin + // store real window size in gWinSize, downscale resolution now + w := round(w / r_pixel_scale); + h := round(h / r_pixel_scale); + e_ResizeFramebuffer(w, h); + end; + gScreenWidth := w; + gScreenHeight := h; {$IFDEF ENABLE_HOLMES} fuiScrWdt := w; fuiScrHgt := h; @@ -120,6 +127,15 @@ implementation begin {$IFDEF NOGL_INIT} nogl_Init; + glRenderToFBO := False; // TODO: check for GL_OES_framebuffer_object + {$ELSE} + if glRenderToFBO then + if not Load_GL_ARB_framebuffer_object() then + if not Load_GL_EXT_framebuffer_object() then + begin + e_LogWriteln('SDL: no framebuffer support detected'); + glRenderToFBO := False + end; {$ENDIF} SDL_WM_SetCaption(GetTitle(), nil); gFullScreen := fullscreen; @@ -136,6 +152,8 @@ implementation procedure sys_Repaint; begin + if glRenderToFBO then + e_BlitFramebuffer(gWinSizeX, gWinSizeY); SDL_GL_SwapBuffers end; diff --git a/src/game/sdl2/g_system.pas b/src/game/sdl2/g_system.pas index 694042c..89cebaf 100644 --- a/src/game/sdl2/g_system.pas +++ b/src/game/sdl2/g_system.pas @@ -78,10 +78,17 @@ implementation begin gWinSizeX := w; gWinSizeY := h; - gScreenWidth := w; - gScreenHeight := h; gRC_Width := w; gRC_Height := h; + if glRenderToFBO then + begin + // store real window size in gWinSize, downscale resolution now + w := round(w / r_pixel_scale); + h := round(h / r_pixel_scale); + e_ResizeFramebuffer(w, h); + end; + gScreenWidth := w; + gScreenHeight := h; {$IFDEF ENABLE_HOLMES} fuiScrWdt := w; fuiScrHgt := h; @@ -151,6 +158,15 @@ implementation begin {$IFDEF NOGL_INIT} nogl_Init; + glRenderToFBO := False; // TODO: check for GL_OES_framebuffer_object + {$ELSE} + if glRenderToFBO then + if not Load_GL_ARB_framebuffer_object() then + if not Load_GL_EXT_framebuffer_object() then + begin + e_LogWriteln('SDL: no framebuffer support detected'); + glRenderToFBO := False + end; {$ENDIF} if (fullscreen = false) and (maximized = false) and (wc = false) then begin @@ -209,6 +225,8 @@ implementation procedure sys_Repaint; begin + if glRenderToFBO then + e_BlitFramebuffer(gWinSizeX, gWinSizeY); SDL_GL_SwapWindow(window) end; diff --git a/src/game/sdl2/g_touch.pas b/src/game/sdl2/g_touch.pas index 23cfbc1..3f999ed 100644 --- a/src/game/sdl2/g_touch.pas +++ b/src/game/sdl2/g_touch.pas @@ -65,7 +65,7 @@ implementation dpi := 96; {$ENDIF} - sz := Trunc(g_touch_size * dpi); sw := gScreenWidth; sh := gScreenHeight; + sz := Trunc(g_touch_size * dpi); sw := gWinSizeX; sh := gWinSizeY; x := 0; y := Round(sh * g_touch_offset / 100); w := sz; h := sz; @@ -274,8 +274,8 @@ implementation Exit; finger := ev.fingerId + 2; - x := Trunc(ev.x * gScreenWidth); - y := Trunc(ev.y * gScreenHeight); + x := Trunc(ev.x * gWinSizeX); + y := Trunc(ev.y * gWinSizeY); for i := VK_FIRSTKEY to VK_LASTKEY do begin -- 2.29.2