From 6f580606cd95e8f2296b45d98ee7fe97a4795aee Mon Sep 17 00:00:00 2001 From: Eidolon Date: Sun, 27 Aug 2023 20:54:31 -0500 Subject: [PATCH] hwr2: Remove pass infrastructure It's not worth trying to force the engine to conform to deferred drawing. --- src/CMakeLists.txt | 2 +- src/d_clisrv.c | 6 +- src/d_main.c | 24 +- src/f_finale.c | 3 + src/f_finale.h | 1 - src/{f_wipe.c => f_wipe.cpp} | 133 ++++- src/g_game.c | 7 +- src/hwr2/CMakeLists.txt | 27 +- ...g_screens.cpp => blit_postimg_screens.cpp} | 103 ++-- ...g_screens.hpp => blit_postimg_screens.hpp} | 22 +- .../{pass_blit_rect.cpp => blit_rect.cpp} | 97 +--- .../{pass_blit_rect.hpp => blit_rect.hpp} | 32 +- src/hwr2/hardware_state.hpp | 46 ++ src/hwr2/pass_resource_managers.cpp | 110 ---- src/hwr2/pass_resource_managers.hpp | 37 -- src/hwr2/pass_screenshot.cpp | 80 --- src/hwr2/patch_atlas.cpp | 73 +-- src/hwr2/patch_atlas.hpp | 18 +- ...s_postprocess.cpp => postprocess_wipe.cpp} | 41 +- ...s_postprocess.hpp => postprocess_wipe.hpp} | 35 +- src/hwr2/resource_management.cpp | 205 +++++++ src/hwr2/resource_management.hpp | 80 +++ src/hwr2/screen_capture.cpp | 59 ++ ...pass_screenshot.hpp => screen_capture.hpp} | 23 +- ...tware.cpp => software_screen_renderer.cpp} | 29 +- ...tware.hpp => software_screen_renderer.hpp} | 21 +- .../{pass_twodee.cpp => twodee_renderer.cpp} | 177 +++--- .../{pass_twodee.hpp => twodee_renderer.hpp} | 78 +-- src/i_video.h | 23 +- src/i_video_common.cpp | 502 ++++-------------- src/k_vote.c | 7 +- src/p_setup.c | 9 +- src/rhi/gl3_core/gl3_core_rhi.cpp | 91 +++- src/rhi/gl3_core/gl3_core_rhi.hpp | 8 +- src/rhi/rhi.hpp | 7 + src/sdl/i_video.cpp | 4 +- src/v_video.cpp | 72 +++ src/v_video.h | 6 + src/y_inter.c | 23 +- 39 files changed, 1125 insertions(+), 1196 deletions(-) rename src/{f_wipe.c => f_wipe.cpp} (79%) rename src/hwr2/{pass_blit_postimg_screens.cpp => blit_postimg_screens.cpp} (90%) rename src/hwr2/{pass_blit_postimg_screens.hpp => blit_postimg_screens.hpp} (73%) rename src/hwr2/{pass_blit_rect.cpp => blit_rect.cpp} (61%) rename src/hwr2/{pass_blit_rect.hpp => blit_rect.hpp} (64%) create mode 100644 src/hwr2/hardware_state.hpp delete mode 100644 src/hwr2/pass_screenshot.cpp rename src/hwr2/{pass_postprocess.cpp => postprocess_wipe.cpp} (89%) rename src/hwr2/{pass_postprocess.hpp => postprocess_wipe.hpp} (61%) create mode 100644 src/hwr2/resource_management.cpp create mode 100644 src/hwr2/resource_management.hpp create mode 100644 src/hwr2/screen_capture.cpp rename src/hwr2/{pass_screenshot.hpp => screen_capture.hpp} (51%) rename src/hwr2/{pass_software.cpp => software_screen_renderer.cpp} (85%) rename src/hwr2/{pass_software.hpp => software_screen_renderer.hpp} (61%) rename src/hwr2/{pass_twodee.cpp => twodee_renderer.cpp} (82%) rename src/hwr2/{pass_twodee.hpp => twodee_renderer.hpp} (70%) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index e555c1cf6..89d4476b0 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -15,7 +15,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32 deh_tables.c z_zone.c f_finale.c - f_wipe.c + f_wipe.cpp g_build_ticcmd.cpp g_demo.c g_game.c diff --git a/src/d_clisrv.c b/src/d_clisrv.c index 3fc62490e..f4b1470fc 100644 --- a/src/d_clisrv.c +++ b/src/d_clisrv.c @@ -2258,11 +2258,13 @@ static boolean CL_ServerConnectionTicker(const char *tmpsave, tic_t *oldtic, tic #endif } I_UpdateNoVsync(); // page flip or blit buffer - I_NewTwodeeFrame(); #ifdef HWRENDER if (moviemode && rendermode == render_opengl) M_LegacySaveFrame(); + else #endif + if (moviemode && rendermode != render_none) + I_CaptureVideoFrame(); S_UpdateSounds(); S_UpdateClosedCaptions(); } @@ -2844,7 +2846,7 @@ void CL_ClearPlayer(INT32 playernum) P_SetTarget(&players[playernum].skybox.viewpoint, NULL); P_SetTarget(&players[playernum].skybox.centerpoint, NULL); P_SetTarget(&players[playernum].awayview.mobj, NULL); - + } // Handle parties. diff --git a/src/d_main.c b/src/d_main.c index 0d80071dc..f4a58b0bb 100644 --- a/src/d_main.c +++ b/src/d_main.c @@ -589,6 +589,12 @@ static void D_Display(void) R_RestoreLevelInterpolators(); } + // rhi: display the software framebuffer to the screen + if (rendermode == render_soft) + { + VID_DisplaySoftwareScreen(); + } + if (lastdraw) { if (rendermode == render_soft) @@ -727,9 +733,6 @@ static void D_Display(void) ps_swaptime = I_GetPreciseTime(); I_FinishUpdate(); // page flip or blit buffer ps_swaptime = I_GetPreciseTime() - ps_swaptime; - - // We should never do the HWR2 skip 3d drawing hack for more than 1 full draw. - g_wipeskiprender = false; } } @@ -829,11 +832,6 @@ void D_SRB2Loop(void) HW3S_BeginFrameUpdate(); #endif - if (rendermode != render_none) - { - I_NewTwodeeFrame(); - } - if (realtics > 0 || singletics) { // don't skip more than 10 frames at a time @@ -910,6 +908,9 @@ void D_SRB2Loop(void) M_DoLegacyGLScreenShot(); #endif + if ((moviemode || takescreenshot) && rendermode == render_soft) + I_CaptureVideoFrame(); + // consoleplayer -> displayplayers (hear sounds from viewpoint) S_UpdateSounds(); // move positional sounds if (realtics > 0 || singletics) @@ -1540,15 +1541,10 @@ void D_SRB2Main(void) CONS_Printf("I_StartupGraphics()...\n"); I_StartupGraphics(); + I_StartDisplayUpdate(); I_StartupInput(); - if (rendermode != render_none) - { - I_NewTwodeeFrame(); - I_NewImguiFrame(); - } - #ifdef HWRENDER // Lactozilla: Add every hardware mode CVAR and CCMD. // Has to be done before the configuration file loads, diff --git a/src/f_finale.c b/src/f_finale.c index 6f3dcaaf6..247042185 100644 --- a/src/f_finale.c +++ b/src/f_finale.c @@ -432,7 +432,10 @@ void F_IntroTicker(void) #ifdef HWRENDER if (moviemode && rendermode == render_opengl) // make sure we save frames for the white hold too M_LegacySaveFrame(); + else #endif + if (moviemode && rendermode != render_none) + I_CaptureVideoFrame(); } } diff --git a/src/f_finale.h b/src/f_finale.h index af71edafe..a40f98a48 100644 --- a/src/f_finale.h +++ b/src/f_finale.h @@ -129,7 +129,6 @@ extern UINT8 g_wipemode; extern UINT8 g_wipetype; extern UINT8 g_wipeframe; extern boolean g_wipereverse; -extern boolean g_wipeskiprender; extern boolean g_wipeencorewiggle; extern boolean WipeStageTitle; diff --git a/src/f_wipe.c b/src/f_wipe.cpp similarity index 79% rename from src/f_wipe.c rename to src/f_wipe.cpp index 48dfc0f60..2831ee19f 100644 --- a/src/f_wipe.c +++ b/src/f_wipe.cpp @@ -45,6 +45,8 @@ // SRB2Kart #include "k_menu.h" +using namespace srb2; + typedef struct fademask_s { UINT8* mask; UINT16 width, height; @@ -216,7 +218,6 @@ UINT8 g_wipemode = 0; UINT8 g_wipetype = 0; UINT8 g_wipeframe = 0; boolean g_wipereverse = false; -boolean g_wipeskiprender = false; boolean g_wipeencorewiggle = false; boolean WipeStageTitle = false; INT32 lastwipetic = 0; @@ -260,7 +261,7 @@ static fademask_t *F_GetFadeMask(UINT8 masknum, UINT8 scrnnum) { if (lumpnum == LUMPERROR) goto freemask; - lump = W_CacheLumpNum(lumpnum, PU_CACHE); + lump = static_cast(W_CacheLumpNum(lumpnum, PU_CACHE)); lsize = W_LumpLength(lumpnum); switch (lsize) { @@ -287,7 +288,7 @@ static fademask_t *F_GetFadeMask(UINT8 masknum, UINT8 scrnnum) { goto freemask; } if (lsize != fm.size) - fm.mask = Z_Realloc(fm.mask, lsize, PU_STATIC, NULL); + fm.mask = reinterpret_cast(Z_Realloc(fm.mask, lsize, PU_STATIC, NULL)); fm.size = lsize; mask = fm.mask; @@ -319,6 +320,38 @@ static fademask_t *F_GetFadeMask(UINT8 masknum, UINT8 scrnnum) { #endif +static void refresh_wipe_screen_texture(rhi::Rhi& rhi, rhi::Handle ctx, rhi::Handle& tex) +{ + bool recreate = false; + if (!tex) + { + recreate = true; + } + else + { + rhi::TextureDetails deets = rhi.get_texture_details(tex); + if (deets.width != static_cast(vid.width) || deets.height != static_cast(vid.height)) + { + recreate = true; + rhi.destroy_texture(tex); + tex = rhi::kNullHandle; + } + } + + if (!recreate) + { + return; + } + + tex = rhi.create_texture({ + rhi::TextureFormat::kRGBA, + static_cast(vid.width), + static_cast(vid.height), + rhi::TextureWrapMode::kClamp, + rhi::TextureWrapMode::kClamp + }); +} + /** Save the "before" screen of a wipe. */ void F_WipeStartScreen(void) @@ -331,9 +364,31 @@ void F_WipeStartScreen(void) return; } #endif - wipe_scr_start = screens[3]; - I_ReadScreen(wipe_scr_start); - I_FinishUpdateWipeStartScreen(); + + rhi::Rhi* rhi = srb2::sys::get_rhi(srb2::sys::g_current_rhi); + + if (!rhi) + { + return; + } + + rhi::Handle ctx = srb2::sys::main_graphics_context(); + + if (!ctx) + { + return; + } + + hwr2::HardwareState* hw_state = srb2::sys::main_hardware_state(); + + refresh_wipe_screen_texture(*rhi, ctx, hw_state->wipe_frames.start); + + hw_state->twodee_renderer->flush(*rhi, ctx, g_2d); + + rhi::Rect copy_region = {0, 0, static_cast(vid.width), static_cast(vid.height)}; + rhi->copy_framebuffer_to_texture(ctx, hw_state->wipe_frames.start, copy_region, copy_region); + + I_FinishUpdate(); #endif } @@ -349,10 +404,36 @@ void F_WipeEndScreen(void) return; } #endif - wipe_scr_end = screens[4]; - I_ReadScreen(wipe_scr_end); - V_DrawBlock(0, 0, 0, vid.width, vid.height, wipe_scr_start); - I_FinishUpdateWipeEndScreen(); + + rhi::Rhi* rhi = srb2::sys::get_rhi(srb2::sys::g_current_rhi); + + if (!rhi) + { + return; + } + + rhi::Handle ctx = srb2::sys::main_graphics_context(); + + if (!ctx) + { + return; + } + + hwr2::HardwareState* hw_state = srb2::sys::main_hardware_state(); + + refresh_wipe_screen_texture(*rhi, ctx, hw_state->wipe_frames.end); + + hw_state->twodee_renderer->flush(*rhi, ctx, g_2d); + + rhi::Rect copy_region = {0, 0, static_cast(vid.width), static_cast(vid.height)}; + rhi->copy_framebuffer_to_texture(ctx, hw_state->wipe_frames.end, copy_region, copy_region); + + hw_state->blit_rect->set_output(copy_region.w, copy_region.h, false, true); + rhi::TextureDetails start_deets = rhi->get_texture_details(hw_state->wipe_frames.start); + hw_state->blit_rect->set_texture(hw_state->wipe_frames.start, start_deets.width, start_deets.height); + hw_state->blit_rect->draw(*rhi, ctx); + + I_FinishUpdate(); #endif } @@ -394,7 +475,7 @@ void F_RunWipe(UINT8 wipemode, UINT8 wipetype, boolean drawMenu, const char *col if (clump != LUMPERROR && wipetype != UINT8_MAX) { pallen = 32; - fcolor = Z_MallocAlign((256 * pallen), PU_STATIC, NULL, 8); + fcolor = static_cast(Z_MallocAlign((256 * pallen), PU_STATIC, NULL, 8)); W_ReadLump(clump, fcolor); } else @@ -407,7 +488,6 @@ void F_RunWipe(UINT8 wipemode, UINT8 wipetype, boolean drawMenu, const char *col // Init the wipe WipeInAction = true; - g_wipeskiprender = false; wipe_scr = screens[0]; // lastwipetic should either be 0 or the tic we last wiped @@ -449,6 +529,23 @@ void F_RunWipe(UINT8 wipemode, UINT8 wipetype, boolean drawMenu, const char *col { g_wipeencorewiggle = 0; } + rhi::Rhi* rhi = srb2::sys::get_rhi(srb2::sys::g_current_rhi); + rhi::Handle ctx = srb2::sys::main_graphics_context(); + hwr2::HardwareState* hw_state = srb2::sys::main_hardware_state(); + + if (reverse) + { + hw_state->wipe->set_start(hw_state->wipe_frames.end); + hw_state->wipe->set_end(hw_state->wipe_frames.start); + } + else + { + hw_state->wipe->set_start(hw_state->wipe_frames.start); + hw_state->wipe->set_end(hw_state->wipe_frames.end); + } + + hw_state->wipe->set_target_size(static_cast(vid.width), static_cast(vid.height)); + hw_state->wipe->draw(*rhi, ctx); } I_OsPolling(); @@ -465,24 +562,20 @@ void F_RunWipe(UINT8 wipemode, UINT8 wipetype, boolean drawMenu, const char *col #endif } - I_FinishUpdateWipe(); // page flip or blit buffer - - if (rendermode != render_none) - { - // Skip subsequent renders until the end of the wipe to preserve the current frame. - g_wipeskiprender = true; - } + I_FinishUpdate(); // page flip or blit buffer #ifdef HWRENDER if (moviemode && rendermode == render_opengl) M_LegacySaveFrame(); + else #endif + if (moviemode && rendermode != render_none) + I_CaptureVideoFrame(); NetKeepAlive(); // Update the network so we don't cause timeouts } WipeInAction = false; - g_wipeskiprender = false; if (fcolor) { diff --git a/src/g_game.c b/src/g_game.c index 3a9008f05..1300f6f31 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -1247,7 +1247,10 @@ void G_PreLevelTitleCard(void) #ifdef HWRENDER if (moviemode && rendermode == render_opengl) M_LegacySaveFrame(); + else #endif + if (moviemode && rendermode != render_none) + I_CaptureVideoFrame(); while (!((nowtime = I_GetTime()) - lasttime)) { @@ -2752,7 +2755,7 @@ mapthing_t *G_FindMapStart(INT32 playernum) if (!playeringame[playernum]) return NULL; - // -- Podium -- + // -- Podium -- // Single special behavior if (K_PodiumSequence() == true) spawnpoint = G_FindPodiumStart(playernum); @@ -4898,7 +4901,7 @@ void G_LoadGameData(void) if (versionMinor < 3) { - // We now require backfilling of placement information. + // We now require backfilling of placement information. cupwindata_t bestwindata; bestwindata.best_placement = 0; diff --git a/src/hwr2/CMakeLists.txt b/src/hwr2/CMakeLists.txt index d961c57e6..eb7febdaf 100644 --- a/src/hwr2/CMakeLists.txt +++ b/src/hwr2/CMakeLists.txt @@ -1,27 +1,30 @@ target_sources(SRB2SDL2 PRIVATE blendmode.hpp - pass_blit_postimg_screens.cpp - pass_blit_postimg_screens.hpp - pass_blit_rect.cpp - pass_blit_rect.hpp + blit_postimg_screens.cpp + blit_postimg_screens.hpp + blit_rect.cpp + blit_rect.hpp + hardware_state.hpp pass_imgui.cpp pass_imgui.hpp pass_manager.cpp pass_manager.hpp - pass_postprocess.cpp - pass_postprocess.hpp pass_resource_managers.cpp pass_resource_managers.hpp - pass_screenshot.cpp - pass_screenshot.hpp - pass_software.cpp - pass_software.hpp - pass_twodee.cpp - pass_twodee.hpp pass.cpp pass.hpp patch_atlas.cpp patch_atlas.hpp + postprocess_wipe.cpp + postprocess_wipe.hpp + resource_management.cpp + resource_management.hpp + screen_capture.cpp + screen_capture.hpp + software_screen_renderer.cpp + software_screen_renderer.hpp twodee.cpp twodee.hpp + twodee_renderer.cpp + twodee_renderer.hpp ) diff --git a/src/hwr2/pass_blit_postimg_screens.cpp b/src/hwr2/blit_postimg_screens.cpp similarity index 90% rename from src/hwr2/pass_blit_postimg_screens.cpp rename to src/hwr2/blit_postimg_screens.cpp index da3b317eb..c3579e9f2 100644 --- a/src/hwr2/pass_blit_postimg_screens.cpp +++ b/src/hwr2/blit_postimg_screens.cpp @@ -7,7 +7,7 @@ // See the 'LICENSE' file for more details. //----------------------------------------------------------------------------- -#include "pass_blit_postimg_screens.hpp" +#include "blit_postimg_screens.hpp" #include #include @@ -68,12 +68,53 @@ static const BlitVertex kVerts[] = static const uint16_t kIndices[] = {0, 1, 2, 1, 3, 2}; -BlitPostimgScreens::BlitPostimgScreens(const std::shared_ptr& palette_mgr) + +static Rect get_screen_viewport(uint32_t screen, uint32_t screens, uint32_t w, uint32_t h) +{ + switch (screens) + { + case 1: + return {0, 0, w, h}; + case 2: + return {0, screen == 0 ? (static_cast(h) / 2) : 0, w, (h / 2)}; + default: + switch (screen) + { + case 2: + return {0, 0, w / 2, h / 2}; + case 3: + return {static_cast(w) / 2, 0, w / 2, h / 2}; + case 0: + return {0, static_cast(h) / 2, w / 2, h / 2}; + case 1: + return {static_cast(w) / 2, static_cast(h) / 2, w / 2, h / 2}; + } + } + return {0, 0, w, h}; +} + +BlitPostimgScreens::BlitPostimgScreens(PaletteManager* palette_mgr) : palette_mgr_(palette_mgr) { } -BlitPostimgScreens::~BlitPostimgScreens() = default; +void BlitPostimgScreens::draw(Rhi& rhi, Handle ctx) +{ + prepass(rhi); + transfer(rhi, ctx); + + for (uint32_t i = 0; i < screens_; i++) + { + BlitPostimgScreens::ScreenData& data = screen_data_[i]; + + rhi.bind_pipeline(ctx, data.pipeline); + rhi.set_viewport(ctx, get_screen_viewport(i, screens_, target_width_, target_height_)); + rhi.bind_uniform_set(ctx, 0, data.uniform_set); + rhi.bind_binding_set(ctx, data.binding_set); + rhi.bind_index_buffer(ctx, quad_ibo_); + rhi.draw_indexed(ctx, 6, 0); + } +} void BlitPostimgScreens::prepass(Rhi& rhi) { @@ -117,30 +158,6 @@ void BlitPostimgScreens::prepass(Rhi& rhi) screen_data_.clear(); } -static Rect get_screen_viewport(uint32_t screen, uint32_t screens, uint32_t w, uint32_t h) -{ - switch (screens) - { - case 1: - return {0, 0, w, h}; - case 2: - return {0, screen == 0 ? (static_cast(h) / 2) : 0, w, (h / 2)}; - default: - switch (screen) - { - case 2: - return {0, 0, w / 2, h / 2}; - case 3: - return {static_cast(w) / 2, 0, w / 2, h / 2}; - case 0: - return {0, static_cast(h) / 2, w / 2, h / 2}; - case 1: - return {static_cast(w) / 2, static_cast(h) / 2, w / 2, h / 2}; - } - } - return {0, 0, w, h}; -} - void BlitPostimgScreens::transfer(Rhi& rhi, Handle ctx) { // Upload needed buffers @@ -206,7 +223,7 @@ void BlitPostimgScreens::transfer(Rhi& rhi, Handle ctx) UniformVariant uniforms[] = { - FixedToFloat(g_time.timefrac) + leveltime, + static_cast(leveltime), projection, modelview, texcoord_transform, @@ -221,33 +238,3 @@ void BlitPostimgScreens::transfer(Rhi& rhi, Handle ctx) screen_data_[i] = std::move(data); } } - -void BlitPostimgScreens::graphics(Rhi& rhi, Handle ctx) -{ - if (target_) - { - rhi.begin_render_pass(ctx, {renderpass_, target_, std::nullopt, glm::vec4(0.0, 0.0, 0.0, 1.0)}); - } - else - { - rhi.begin_default_render_pass(ctx, true); - } - - for (uint32_t i = 0; i < screens_; i++) - { - BlitPostimgScreens::ScreenData& data = screen_data_[i]; - - rhi.bind_pipeline(ctx, data.pipeline); - rhi.set_viewport(ctx, get_screen_viewport(i, screens_, target_ ? target_width_ : vid.width, target_ ? target_height_ : vid.height)); - rhi.bind_uniform_set(ctx, 0, data.uniform_set); - rhi.bind_binding_set(ctx, data.binding_set); - rhi.bind_index_buffer(ctx, quad_ibo_); - rhi.draw_indexed(ctx, 6, 0); - } - - rhi.end_render_pass(ctx); -} - -void BlitPostimgScreens::postpass(Rhi& rhi) -{ -} diff --git a/src/hwr2/pass_blit_postimg_screens.hpp b/src/hwr2/blit_postimg_screens.hpp similarity index 73% rename from src/hwr2/pass_blit_postimg_screens.hpp rename to src/hwr2/blit_postimg_screens.hpp index 382995d58..df5904e5d 100644 --- a/src/hwr2/pass_blit_postimg_screens.hpp +++ b/src/hwr2/blit_postimg_screens.hpp @@ -17,13 +17,12 @@ #include "../rhi/rhi.hpp" #include "../doomdef.h" -#include "pass.hpp" -#include "pass_resource_managers.hpp" +#include "resource_management.hpp" namespace srb2::hwr2 { -class BlitPostimgScreens : public Pass +class BlitPostimgScreens { public: struct PostImgConfig @@ -61,20 +60,18 @@ private: uint32_t screens_; std::array screen_configs_; srb2::StaticVec screen_data_; - rhi::Handle target_; uint32_t target_width_; uint32_t target_height_; - std::shared_ptr palette_mgr_; + PaletteManager* palette_mgr_; + + void prepass(rhi::Rhi& rhi); + void transfer(rhi::Rhi& rhi, rhi::Handle ctx); public: - BlitPostimgScreens(const std::shared_ptr& palette_mgr); - virtual ~BlitPostimgScreens(); + explicit BlitPostimgScreens(PaletteManager* palette_mgr); - virtual void prepass(rhi::Rhi& rhi) override; - virtual void transfer(rhi::Rhi& rhi, rhi::Handle ctx) override; - virtual void graphics(rhi::Rhi& rhi, rhi::Handle ctx) override; - virtual void postpass(rhi::Rhi& rhi) override; + void draw(rhi::Rhi& rhi, rhi::Handle ctx); void set_num_screens(uint32_t screens) noexcept { @@ -88,9 +85,8 @@ public: screen_configs_[screen_index] = config; } - void set_target(rhi::Handle target, uint32_t width, uint32_t height) noexcept + void set_target(uint32_t width, uint32_t height) noexcept { - target_ = target; target_width_ = width; target_height_ = height; } diff --git a/src/hwr2/pass_blit_rect.cpp b/src/hwr2/blit_rect.cpp similarity index 61% rename from src/hwr2/pass_blit_rect.cpp rename to src/hwr2/blit_rect.cpp index 89971b456..a5101e0f0 100644 --- a/src/hwr2/pass_blit_rect.cpp +++ b/src/hwr2/blit_rect.cpp @@ -7,7 +7,7 @@ // See the 'LICENSE' file for more details. //----------------------------------------------------------------------------- -#include "pass_blit_rect.hpp" +#include "blit_rect.hpp" #include @@ -37,22 +37,6 @@ static const BlitVertex kVerts[] = static const uint16_t kIndices[] = {0, 1, 2, 1, 3, 2}; -/// @brief Pipeline used for paletted source textures. Requires the texture and the palette texture. -static const PipelineDesc kPalettedPipelineDescription = { - PipelineProgram::kUnshadedPaletted, - {{{sizeof(BlitVertex)}}, {{VertexAttributeName::kPosition, 0, 0}, {VertexAttributeName::kTexCoord0, 0, 12}}}, - {{{{UniformName::kProjection}}, {{UniformName::kModelView, UniformName::kTexCoord0Transform}}}}, - {{// R8 index texture - SamplerName::kSampler0, - // 256x1 palette texture - SamplerName::kSampler1}}, - std::nullopt, - {std::nullopt, {true, true, true, true}}, - PrimitiveType::kTriangles, - CullMode::kNone, - FaceWinding::kCounterClockwise, - {0.f, 0.f, 0.f, 1.f}}; - /// @brief Pipeline used for non-paletted source textures. static const PipelineDesc kUnshadedPipelineDescription = { PipelineProgram::kUnshaded, @@ -67,33 +51,21 @@ static const PipelineDesc kUnshadedPipelineDescription = { FaceWinding::kCounterClockwise, {0.f, 0.f, 0.f, 1.f}}; -BlitRectPass::BlitRectPass() : Pass() -{ -} - -BlitRectPass::BlitRectPass(bool output_clear) : Pass(), output_clear_(output_clear) -{ -} - -BlitRectPass::BlitRectPass(const std::shared_ptr& palette_mgr, bool output_clear) - : Pass(), output_clear_(output_clear), palette_mgr_(palette_mgr) -{ -} - +BlitRectPass::BlitRectPass() = default; BlitRectPass::~BlitRectPass() = default; +void BlitRectPass::draw(Rhi& rhi, Handle ctx) +{ + prepass(rhi); + transfer(rhi, ctx); + graphics(rhi, ctx); +} + void BlitRectPass::prepass(Rhi& rhi) { if (!pipeline_) { - if (palette_mgr_) - { - pipeline_ = rhi.create_pipeline(kPalettedPipelineDescription); - } - else - { - pipeline_ = rhi.create_pipeline(kUnshadedPipelineDescription); - } + pipeline_ = rhi.create_pipeline(kUnshadedPipelineDescription); } if (!quad_vbo_) @@ -107,21 +79,6 @@ void BlitRectPass::prepass(Rhi& rhi) quad_ibo_ = rhi.create_buffer({sizeof(kIndices), BufferType::kIndexBuffer, BufferUsage::kImmutable}); quad_ibo_needs_upload_ = true; } - - if (!render_pass_) - { - render_pass_ = rhi.create_render_pass( - { - false, - output_clear_ ? AttachmentLoadOp::kClear : AttachmentLoadOp::kLoad, - AttachmentStoreOp::kStore, - AttachmentLoadOp::kDontCare, - AttachmentStoreOp::kDontCare, - AttachmentLoadOp::kDontCare, - AttachmentStoreOp::kDontCare - } - ); - } } void BlitRectPass::transfer(Rhi& rhi, Handle ctx) @@ -173,45 +130,17 @@ void BlitRectPass::transfer(Rhi& rhi, Handle ctx) uniform_sets_[1] = rhi.create_uniform_set(ctx, {g2_uniforms}); std::array vbs = {{{0, quad_vbo_}}}; - if (palette_mgr_) - { - std::array tbs = { - {{rhi::SamplerName::kSampler0, texture_}, {rhi::SamplerName::kSampler1, palette_mgr_->palette()}}}; - binding_set_ = rhi.create_binding_set(ctx, pipeline_, {vbs, tbs}); - } - else - { - std::array tbs = {{{rhi::SamplerName::kSampler0, texture_}}}; - binding_set_ = rhi.create_binding_set(ctx, pipeline_, {vbs, tbs}); - } + std::array tbs = {{{rhi::SamplerName::kSampler0, texture_}}}; + binding_set_ = rhi.create_binding_set(ctx, pipeline_, {vbs, tbs}); } -static constexpr const glm::vec4 kClearColor = {0, 0, 0, 1}; - void BlitRectPass::graphics(Rhi& rhi, Handle ctx) { - if (output_) - { - rhi.begin_render_pass(ctx, {render_pass_, output_, std::nullopt, kClearColor}); - } - else - { - rhi.begin_default_render_pass(ctx, output_clear_); - } - rhi.bind_pipeline(ctx, pipeline_); - if (output_) - { - rhi.set_viewport(ctx, {0, 0, output_width_, output_height_}); - } + rhi.set_viewport(ctx, {0, 0, output_width_, output_height_}); rhi.bind_uniform_set(ctx, 0, uniform_sets_[0]); rhi.bind_uniform_set(ctx, 1, uniform_sets_[1]); rhi.bind_binding_set(ctx, binding_set_); rhi.bind_index_buffer(ctx, quad_ibo_); rhi.draw_indexed(ctx, 6, 0); - rhi.end_render_pass(ctx); -} - -void BlitRectPass::postpass(Rhi& rhi) -{ } diff --git a/src/hwr2/pass_blit_rect.hpp b/src/hwr2/blit_rect.hpp similarity index 64% rename from src/hwr2/pass_blit_rect.hpp rename to src/hwr2/blit_rect.hpp index f0719b70e..014a876bb 100644 --- a/src/hwr2/pass_blit_rect.hpp +++ b/src/hwr2/blit_rect.hpp @@ -7,20 +7,19 @@ // See the 'LICENSE' file for more details. //----------------------------------------------------------------------------- -#ifndef __SRB2_HWR2_PASS_BLIT_RECT_HPP__ -#define __SRB2_HWR2_PASS_BLIT_RECT_HPP__ +#ifndef __SRB2_HWR2_BLIT_RECT_HPP__ +#define __SRB2_HWR2_BLIT_RECT_HPP__ #include #include "../rhi/rhi.hpp" -#include "pass.hpp" -#include "pass_resource_managers.hpp" +#include "resource_management.hpp" namespace srb2::hwr2 { /// @brief A render pass which blits a rect using a source texture or textures. -class BlitRectPass final : public Pass +class BlitRectPass { rhi::Handle pipeline_; rhi::Handle texture_; @@ -30,9 +29,7 @@ class BlitRectPass final : public Pass uint32_t output_width_ = 0; uint32_t output_height_ = 0; bool output_correct_aspect_ = false; - bool output_clear_ = false; bool output_flip_ = false; - rhi::Handle render_pass_; rhi::Handle quad_vbo_; rhi::Handle quad_ibo_; std::array, 2> uniform_sets_; @@ -41,19 +38,15 @@ class BlitRectPass final : public Pass bool quad_vbo_needs_upload_ = false; bool quad_ibo_needs_upload_ = false; - // The presence of a palette manager indicates that the source texture will be paletted. This can't be changed. - std::shared_ptr palette_mgr_; + void prepass(rhi::Rhi& rhi); + void transfer(rhi::Rhi& rhi, rhi::Handle ctx); + void graphics(rhi::Rhi& rhi, rhi::Handle ctx); public: BlitRectPass(); - BlitRectPass(bool output_clear); - BlitRectPass(const std::shared_ptr& palette_mgr, bool output_clear); - virtual ~BlitRectPass(); + ~BlitRectPass(); - virtual void prepass(rhi::Rhi& rhi) override; - virtual void transfer(rhi::Rhi& rhi, rhi::Handle ctx) override; - virtual void graphics(rhi::Rhi& rhi, rhi::Handle ctx) override; - virtual void postpass(rhi::Rhi& rhi) override; + void draw(rhi::Rhi& rhi, rhi::Handle ctx); /// @brief Set the next blit texture. Don't call during graphics phase! /// @param texture the texture to use when blitting @@ -67,27 +60,22 @@ public: } /// @brief Set the next output texture. Don't call during graphics phase! - /// @param texture the texture to use as a color buffer /// @param width texture width /// @param height texture height void set_output( - rhi::Handle color, uint32_t width, uint32_t height, bool correct_aspect, bool flip ) noexcept { - output_ = color; output_width_ = width; output_height_ = height; output_correct_aspect_ = correct_aspect; output_flip_ = flip; } - - void clear_output(bool clear) noexcept { output_clear_ = clear; } }; } // namespace srb2::hwr2 -#endif // __SRB2_HWR2_PASS_SOFTWARE_HPP__ +#endif // __SRB2_HWR2_BLIT_RECT_HPP__ diff --git a/src/hwr2/hardware_state.hpp b/src/hwr2/hardware_state.hpp new file mode 100644 index 000000000..bc42dcf08 --- /dev/null +++ b/src/hwr2/hardware_state.hpp @@ -0,0 +1,46 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_HWR2_HARDWARE_STATE_HPP__ +#define __SRB2_HWR2_HARDWARE_STATE_HPP__ + +#include "blit_postimg_screens.hpp" +#include "blit_rect.hpp" +#include "postprocess_wipe.hpp" +#include "resource_management.hpp" +#include "screen_capture.hpp" +#include "software_screen_renderer.hpp" +#include "twodee_renderer.hpp" + +namespace srb2::hwr2 +{ + +struct WipeFrames +{ + rhi::Handle start; + rhi::Handle end; +}; + +struct HardwareState +{ + std::unique_ptr palette_manager; + std::unique_ptr flat_manager; + std::unique_ptr patch_atlas_cache; + std::unique_ptr twodee_renderer; + std::unique_ptr software_screen_renderer; + std::unique_ptr blit_postimg_screens; + std::unique_ptr wipe; + std::unique_ptr blit_rect; + std::unique_ptr screen_capture; + WipeFrames wipe_frames; +}; + +} // srb2::hwr2 + +#endif // __SRB2_HWR2_HARDWARE_STATE_HPP__ diff --git a/src/hwr2/pass_resource_managers.cpp b/src/hwr2/pass_resource_managers.cpp index 99a05ca26..78b8449d8 100644 --- a/src/hwr2/pass_resource_managers.cpp +++ b/src/hwr2/pass_resource_managers.cpp @@ -352,113 +352,3 @@ void CommonResourcesManager::postpass(Rhi& rhi) { init_ = true; } - -static uint32_t get_flat_size(lumpnum_t lump) -{ - SRB2_ASSERT(lump != LUMPERROR); - - std::size_t lumplength = W_LumpLength(lump); - if (lumplength == 0) - { - return 0; - } - - if ((lumplength & (lumplength - 1)) != 0) - { - // Lump length is not a power of two and therefore not a flat. - return 0; - } - uint32_t lumpsize = std::pow(2, std::log2(lumplength) / 2); - return lumpsize; -} - -FlatTextureManager::FlatTextureManager() : Pass() -{ -} - -FlatTextureManager::~FlatTextureManager() = default; - -void FlatTextureManager::prepass(Rhi& rhi) -{ -} - -void FlatTextureManager::transfer(Rhi& rhi, Handle ctx) -{ - std::vector> flat_data; - for (auto flat_lump : to_upload_) - { - flat_data.clear(); - Handle flat_texture = flats_[flat_lump]; - SRB2_ASSERT(flat_texture != kNullHandle); - std::size_t lump_length = W_LumpLength(flat_lump); - uint32_t flat_size = get_flat_size(flat_lump); - flat_data.reserve(flat_size * flat_size); - - const uint8_t* flat_memory = static_cast(W_CacheLumpNum(flat_lump, PU_PATCH)); - SRB2_ASSERT(flat_memory != nullptr); - - tcb::span flat_bytes = tcb::span(flat_memory, lump_length); - for (const uint8_t index : flat_bytes) - { - // The alpha/green channel is set to 0 if it's index 247; this is not usually used but fake floors can be - // masked sometimes, so we need to treat it as transparent when rendering them. - // See https://zdoom.org/wiki/Palette for remarks on fake 247 transparency - flat_data.push_back({index, index == 247 ? static_cast(0) : static_cast(255)}); - } - - // A flat size of 1 would end up being 2 bytes, so we need 2 more bytes to be unpack-aligned on texture upload - // Any other size would implicitly be aligned. - // Sure hope nobody tries to load any flats that are too big for the gpu! - if (flat_size == 1) - { - flat_data.push_back({0, 0}); - } - - tcb::span data_bytes = tcb::as_bytes(tcb::span(flat_data)); - rhi.update_texture(ctx, flat_texture, {0, 0, flat_size, flat_size}, rhi::PixelFormat::kRG8, data_bytes); - } - to_upload_.clear(); -} - -void FlatTextureManager::graphics(Rhi& rhi, Handle ctx) -{ -} - -void FlatTextureManager::postpass(Rhi& rhi) -{ -} - -Handle FlatTextureManager::find_or_create_indexed(Rhi& rhi, lumpnum_t lump) -{ - SRB2_ASSERT(lump != LUMPERROR); - - auto flat_itr = flats_.find(lump); - if (flat_itr != flats_.end()) - { - return flat_itr->second; - } - - uint32_t flat_size = get_flat_size(lump); - Handle new_tex = rhi.create_texture({ - TextureFormat::kLuminanceAlpha, - flat_size, - flat_size, - TextureWrapMode::kRepeat, - TextureWrapMode::kRepeat - }); - flats_.insert({lump, new_tex}); - to_upload_.push_back(lump); - return new_tex; -} - -Handle FlatTextureManager::find_indexed(lumpnum_t lump) const -{ - SRB2_ASSERT(lump != LUMPERROR); - - auto flat_itr = flats_.find(lump); - if (flat_itr != flats_.end()) - { - return flat_itr->second; - } - return kNullHandle; -} diff --git a/src/hwr2/pass_resource_managers.hpp b/src/hwr2/pass_resource_managers.hpp index ff7cce2d2..0f88849e3 100644 --- a/src/hwr2/pass_resource_managers.hpp +++ b/src/hwr2/pass_resource_managers.hpp @@ -129,43 +129,6 @@ public: rhi::Handle transparent() const noexcept { return transparent_; } }; -/* -A note to the reader: - -RHI/HWR2's architecture is intentionally decoupled in a data-oriented design fashion. Hash map lookups might technically -be slower than storing the RHI handle in a hypothetical Flat class object, but it frees us from worrying about the -validity of a given Handle when the RHI instance changes -- and it _can_, because this is designed to allow multiple -RHI backends -- because any given Pass must be disposed when the RHI changes. The implementation of I_FinishUpdate is -such that if the RHI is not the same as before, all passes must be reconstructed, and so we don't have to worry about -going around and resetting Handle references everywhere. If you're familiar with old GL, it's like decoupling GLmipmap_t -from patch_t. -*/ - -/// @brief Manages textures corresponding to specific flats indexed by lump number. -class FlatTextureManager final : public Pass -{ - std::unordered_map> flats_; - std::vector to_upload_; - std::vector> disposed_textures_; - -public: - FlatTextureManager(); - virtual ~FlatTextureManager(); - - virtual void prepass(rhi::Rhi& rhi) override; - virtual void transfer(rhi::Rhi& rhi, rhi::Handle ctx) override; - virtual void graphics(rhi::Rhi& rhi, rhi::Handle ctx) override; - virtual void postpass(rhi::Rhi& rhi) override; - - /// @brief Find the indexed texture for a given flat lump, or create one if it doesn't exist yet. Only call this - /// in prepass. - /// @param flat_lump - /// @return - rhi::Handle find_or_create_indexed(rhi::Rhi& rhi, lumpnum_t flat_lump); - - rhi::Handle find_indexed(lumpnum_t flat_lump) const; -}; - } // namespace srb2::hwr2 #endif // __SRB2_HWR2_PASS_RESOURCE_MANAGERS_HPP__ diff --git a/src/hwr2/pass_screenshot.cpp b/src/hwr2/pass_screenshot.cpp deleted file mode 100644 index cff5cc99a..000000000 --- a/src/hwr2/pass_screenshot.cpp +++ /dev/null @@ -1,80 +0,0 @@ -// SONIC ROBO BLAST 2 -//----------------------------------------------------------------------------- -// Copyright (C) 2023 by Ronald "Eidolon" Kinard -// -// This program is free software distributed under the -// terms of the GNU General Public License, version 2. -// See the 'LICENSE' file for more details. -//----------------------------------------------------------------------------- - -#include "pass_screenshot.hpp" - -#include - -#include "../m_misc.h" - -using namespace srb2; -using namespace srb2::hwr2; -using namespace srb2::rhi; - -ScreenshotPass::ScreenshotPass() = default; -ScreenshotPass::~ScreenshotPass() = default; - -void ScreenshotPass::prepass(Rhi& rhi) -{ - if (!render_pass_) - { - render_pass_ = rhi.create_render_pass( - { - false, - AttachmentLoadOp::kLoad, - AttachmentStoreOp::kStore, - AttachmentLoadOp::kDontCare, - AttachmentStoreOp::kDontCare, - AttachmentLoadOp::kDontCare, - AttachmentStoreOp::kDontCare - } - ); - } - - doing_screenshot_ = takescreenshot || moviemode != MM_OFF; -} - -void ScreenshotPass::transfer(Rhi& rhi, Handle ctx) -{ -} - -void ScreenshotPass::graphics(Rhi& rhi, Handle ctx) -{ - if (!doing_screenshot_) - { - return; - } - - pixel_data_.clear(); - pixel_data_.resize(width_ * height_ * 3); // 3 bytes per pixel for RGB8 - - rhi.begin_render_pass(ctx, {render_pass_, source_, std::nullopt, {0.f, 0.f, 0.f, 0.f}}); - rhi.read_pixels(ctx, {0, 0, width_, height_}, PixelFormat::kRGB8, tcb::as_writable_bytes(tcb::span(pixel_data_))); - rhi.end_render_pass(ctx); -} - -void ScreenshotPass::postpass(Rhi& rhi) -{ - if (!doing_screenshot_) - { - return; - } - - if (takescreenshot) - { - M_DoScreenShot(width_, height_, tcb::as_bytes(tcb::span(pixel_data_))); - } - - if (moviemode != MM_OFF) - { - M_SaveFrame(width_, height_, tcb::as_bytes(tcb::span(pixel_data_))); - } - - doing_screenshot_ = false; -} diff --git a/src/hwr2/patch_atlas.cpp b/src/hwr2/patch_atlas.cpp index 26e35ca4b..25e40cdb7 100644 --- a/src/hwr2/patch_atlas.cpp +++ b/src/hwr2/patch_atlas.cpp @@ -215,7 +215,7 @@ static PatchAtlas create_atlas(Rhi& rhi, uint32_t size) return new_atlas; } -void PatchAtlasCache::pack(Rhi& rhi) +void PatchAtlasCache::pack(Rhi& rhi, Handle ctx) { // Prepare stbrp rects for patches to be loaded. std::vector rects; @@ -291,9 +291,34 @@ void PatchAtlasCache::pack(Rhi& rhi) } } + patches_to_pack_.clear(); + // TODO Create large patch "atlases" - patches_to_pack_.clear(); + SRB2_ASSERT(ready_for_lookup()); + + // Upload atlased patches + std::vector patch_data; + for (const patch_t* patch_to_upload : patches_to_upload_) + { + srb2::NotNull atlas = find_patch(patch_to_upload); + + std::optional entry = atlas->find_patch(patch_to_upload); + SRB2_ASSERT(entry.has_value()); + + convert_patch_to_trimmed_rg8_pixels(patch_to_upload, patch_data); + + rhi.update_texture( + ctx, + atlas->tex_, + {static_cast(entry->x), static_cast(entry->y), entry->w, entry->h}, + PixelFormat::kRG8, + tcb::as_bytes(tcb::span(patch_data)) + ); + + patch_data.clear(); + } + patches_to_upload_.clear(); } PatchAtlas* PatchAtlasCache::find_patch(srb2::NotNull patch) @@ -339,47 +364,3 @@ void PatchAtlasCache::queue_patch(srb2::NotNull patch) patches_to_pack_.insert(patch); } - -void PatchAtlasCache::prepass(Rhi& rhi) -{ - if (need_to_reset()) - { - reset(rhi); - } -} - -void PatchAtlasCache::transfer(Rhi& rhi, Handle ctx) -{ - SRB2_ASSERT(ready_for_lookup()); - - // Upload atlased patches - std::vector patch_data; - for (const patch_t* patch_to_upload : patches_to_upload_) - { - srb2::NotNull atlas = find_patch(patch_to_upload); - - std::optional entry = atlas->find_patch(patch_to_upload); - SRB2_ASSERT(entry.has_value()); - - convert_patch_to_trimmed_rg8_pixels(patch_to_upload, patch_data); - - rhi.update_texture( - ctx, - atlas->tex_, - {static_cast(entry->x), static_cast(entry->y), entry->w, entry->h}, - PixelFormat::kRG8, - tcb::as_bytes(tcb::span(patch_data)) - ); - - patch_data.clear(); - } - patches_to_upload_.clear(); -} - -void PatchAtlasCache::graphics(Rhi& rhi, Handle ctx) -{ -} - -void PatchAtlasCache::postpass(Rhi& rhi) -{ -} diff --git a/src/hwr2/patch_atlas.hpp b/src/hwr2/patch_atlas.hpp index 85b3b1162..7ed001db7 100644 --- a/src/hwr2/patch_atlas.hpp +++ b/src/hwr2/patch_atlas.hpp @@ -81,7 +81,7 @@ public: /// @brief A resource-managing pass which creates and manages a set of Atlas Textures with /// optimally packed Patches, allowing drawing passes to reuse the same texture binds for /// drawing things like sprites and 2D elements. -class PatchAtlasCache : public Pass +class PatchAtlasCache { std::vector atlases_; std::unordered_map patch_lookup_; @@ -92,10 +92,6 @@ class PatchAtlasCache : public Pass uint32_t tex_size_ = 2048; size_t max_textures_ = 2; - bool need_to_reset() const; - - /// @brief Clear the atlases and reset for lookup. - void reset(rhi::Rhi& rhi); bool ready_for_lookup() const; /// @brief Decide if a rect's dimensions are Large, that is, the rect should not be packed and instead its patch @@ -109,14 +105,14 @@ public: PatchAtlasCache(PatchAtlasCache&&); PatchAtlasCache& operator=(const PatchAtlasCache&) = delete; PatchAtlasCache& operator=(PatchAtlasCache&&); - virtual ~PatchAtlasCache(); + ~PatchAtlasCache(); /// @brief Queue a patch to be packed. All patches will be packed after the prepass phase, /// or the owner can explicitly request a pack. void queue_patch(srb2::NotNull patch); /// @brief Pack queued patches, allowing them to be looked up with find_patch. - void pack(rhi::Rhi& rhi); + void pack(rhi::Rhi& rhi, rhi::Handle ctx); /// @brief Find the atlas a patch belongs to, or nullopt if it is not cached. /// This may not be called if there are still patches that need to be packed. @@ -124,10 +120,10 @@ public: const PatchAtlas* find_patch(srb2::NotNull patch) const; PatchAtlas* find_patch(srb2::NotNull patch); - virtual void prepass(rhi::Rhi& rhi) override; - virtual void transfer(rhi::Rhi& rhi, rhi::Handle ctx) override; - virtual void graphics(rhi::Rhi& rhi, rhi::Handle ctx) override; - virtual void postpass(rhi::Rhi& rhi) override; + bool need_to_reset() const; + + /// @brief Clear the atlases and reset for lookup. + void reset(rhi::Rhi& rhi); }; /// @brief Calculate the subregion of the patch which excludes empty space on the borders. diff --git a/src/hwr2/pass_postprocess.cpp b/src/hwr2/postprocess_wipe.cpp similarity index 89% rename from src/hwr2/pass_postprocess.cpp rename to src/hwr2/postprocess_wipe.cpp index 0f2eefdc2..581e1cf88 100644 --- a/src/hwr2/pass_postprocess.cpp +++ b/src/hwr2/postprocess_wipe.cpp @@ -7,7 +7,7 @@ // See the 'LICENSE' file for more details. //----------------------------------------------------------------------------- -#include "pass_postprocess.hpp" +#include "postprocess_wipe.hpp" #include @@ -62,23 +62,16 @@ PostprocessWipePass::PostprocessWipePass() PostprocessWipePass::~PostprocessWipePass() = default; +void PostprocessWipePass::draw(Rhi& rhi, Handle ctx) +{ + prepass(rhi); + transfer(rhi, ctx); + graphics(rhi, ctx); + postpass(rhi); +} + void PostprocessWipePass::prepass(Rhi& rhi) { - if (!render_pass_) - { - render_pass_ = rhi.create_render_pass( - { - false, - AttachmentLoadOp::kLoad, - AttachmentStoreOp::kStore, - AttachmentLoadOp::kDontCare, - AttachmentStoreOp::kDontCare, - AttachmentLoadOp::kDontCare, - AttachmentStoreOp::kDontCare - } - ); - } - if (!pipeline_) { pipeline_ = rhi.create_pipeline(kWipePipelineDesc); @@ -224,26 +217,12 @@ void PostprocessWipePass::graphics(Rhi& rhi, Handle ctx) return; } - if (target_) - { - rhi.begin_render_pass(ctx, {render_pass_, target_, std::nullopt, {0, 0, 0, 1}}); - } - else - { - rhi.begin_default_render_pass(ctx, false); - } - rhi.bind_pipeline(ctx, pipeline_); - if (target_) - { - rhi.set_viewport(ctx, {0, 0, target_w_, target_h_}); - } + rhi.set_viewport(ctx, {0, 0, width_, height_}); rhi.bind_uniform_set(ctx, 0, us_); rhi.bind_binding_set(ctx, bs_); rhi.bind_index_buffer(ctx, ibo_); rhi.draw_indexed(ctx, 6, 0); - - rhi.end_render_pass(ctx); } void PostprocessWipePass::postpass(Rhi& rhi) diff --git a/src/hwr2/pass_postprocess.hpp b/src/hwr2/postprocess_wipe.hpp similarity index 61% rename from src/hwr2/pass_postprocess.hpp rename to src/hwr2/postprocess_wipe.hpp index f0deb7ff9..42c5df07b 100644 --- a/src/hwr2/pass_postprocess.hpp +++ b/src/hwr2/postprocess_wipe.hpp @@ -7,20 +7,19 @@ // See the 'LICENSE' file for more details. //----------------------------------------------------------------------------- -#ifndef __SRB2_HWR2_PASS_POSTPROCESS_HPP__ -#define __SRB2_HWR2_PASS_POSTPROCESS_HPP__ - -#include "pass.hpp" +#ifndef __SRB2_HWR2_POSTPROCESS_WIPE_HPP__ +#define __SRB2_HWR2_POSTPROCESS_WIPE_HPP__ #include +#include "../rhi/rhi.hpp" + namespace srb2::hwr2 { -class PostprocessWipePass final : public Pass +class PostprocessWipePass final { // Internal RHI resources - rhi::Handle render_pass_; rhi::Handle pipeline_; rhi::Handle vbo_; bool upload_vbo_ = false; @@ -35,36 +34,36 @@ class PostprocessWipePass final : public Pass // Pass parameters rhi::Handle start_; rhi::Handle end_; - rhi::Handle target_; - uint32_t target_w_ = 0; - uint32_t target_h_ = 0; + uint32_t width_; + uint32_t height_; // Mask lump loading std::vector mask_data_; uint32_t mask_w_ = 0; uint32_t mask_h_ = 0; + void prepass(rhi::Rhi& rhi); + void transfer(rhi::Rhi& rhi, rhi::Handle ctx); + void graphics(rhi::Rhi& rhi, rhi::Handle ctx); + void postpass(rhi::Rhi& rhi); + public: PostprocessWipePass(); virtual ~PostprocessWipePass(); - virtual void prepass(rhi::Rhi& rhi) override; - virtual void transfer(rhi::Rhi& rhi, rhi::Handle ctx) override; - virtual void graphics(rhi::Rhi& rhi, rhi::Handle ctx) override; - virtual void postpass(rhi::Rhi& rhi) override; + void draw(rhi::Rhi& rhi, rhi::Handle ctx); void set_start(rhi::Handle start) noexcept { start_ = start; } void set_end(rhi::Handle end) noexcept { end_ = end; } - void set_target(rhi::Handle target, uint32_t width, uint32_t height) noexcept + void set_target_size(uint32_t width, uint32_t height) noexcept { - target_ = target; - target_w_ = width; - target_h_ = height; + width_ = width; + height_ = height; } }; } // namespace srb2::hwr2 -#endif // __SRB2_HWR2_PASS_POSTPROCESS_HPP__ +#endif // __SRB2_HWR2_POSTPROCESS_WIPE_HPP__ diff --git a/src/hwr2/resource_management.cpp b/src/hwr2/resource_management.cpp new file mode 100644 index 000000000..b16c366d0 --- /dev/null +++ b/src/hwr2/resource_management.cpp @@ -0,0 +1,205 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "resource_management.hpp" + +#include "../r_state.h" +#include "../v_video.h" +#include "../z_zone.h" + +using namespace srb2; +using namespace rhi; +using namespace hwr2; + +PaletteManager::PaletteManager() = default; +PaletteManager::PaletteManager(PaletteManager&&) = default; +PaletteManager::~PaletteManager() = default; +PaletteManager& PaletteManager::operator=(PaletteManager&&) = default; + +constexpr std::size_t kPaletteSize = 256; +constexpr std::size_t kLighttableRows = LIGHTLEVELS; + +void PaletteManager::update(Rhi& rhi, Handle ctx) +{ + if (!palette_) + { + palette_ = rhi.create_texture({TextureFormat::kRGBA, kPaletteSize, 1, TextureWrapMode::kClamp, TextureWrapMode::kClamp}); + } + + if (!lighttable_) + { + lighttable_ = rhi.create_texture({TextureFormat::kLuminance, kPaletteSize, kLighttableRows, TextureWrapMode::kClamp, TextureWrapMode::kClamp}); + } + + if (!encore_lighttable_) + { + encore_lighttable_ = rhi.create_texture({TextureFormat::kLuminance, kPaletteSize, kLighttableRows, TextureWrapMode::kClamp, TextureWrapMode::kClamp}); + } + + if (!default_colormap_) + { + default_colormap_ = rhi.create_texture({TextureFormat::kLuminance, kPaletteSize, 1, TextureWrapMode::kClamp, TextureWrapMode::kClamp}); + } + + // Palette + { + std::array palette_32; + for (std::size_t i = 0; i < kPaletteSize; i++) + { + palette_32[i] = V_GetColor(i).s; + } + rhi.update_texture(ctx, palette_, {0, 0, kPaletteSize, 1}, PixelFormat::kRGBA8, tcb::as_bytes(tcb::span(palette_32))); + } + + // Lighttables + { + if (colormaps != nullptr) + { + tcb::span colormap_bytes = tcb::as_bytes(tcb::span(colormaps, kPaletteSize * kLighttableRows)); + rhi.update_texture(ctx, lighttable_, {0, 0, kPaletteSize, kLighttableRows}, PixelFormat::kR8, colormap_bytes); + } + + if (encoremap != nullptr) + { + tcb::span encoremap_bytes = tcb::as_bytes(tcb::span(encoremap, kPaletteSize * kLighttableRows)); + rhi.update_texture(ctx, encore_lighttable_, {0, 0, kPaletteSize, kLighttableRows}, PixelFormat::kR8, encoremap_bytes); + } + } + + // Default colormap + { + std::array data; + for (std::size_t i = 0; i < kPaletteSize; i++) + { + data[i] = i; + } + rhi.update_texture(ctx, default_colormap_, {0, 0, kPaletteSize, 1}, PixelFormat::kR8, tcb::as_bytes(tcb::span(data))); + } +} + +void PaletteManager::destroy_per_frame_resources(Rhi& rhi) +{ + for (auto colormap_tex : colormaps_) + { + rhi.destroy_texture(colormap_tex.second); + } + + colormaps_.clear(); + + for (auto lighttable_tex : lighttables_) + { + rhi.destroy_texture(lighttable_tex.second); + } + + lighttables_.clear(); +} + +Handle PaletteManager::find_or_create_colormap(Rhi& rhi, rhi::Handle ctx, srb2::NotNull colormap) +{ + if (colormaps_.find(colormap) != colormaps_.end()) + { + return colormaps_[colormap]; + } + + Handle texture = rhi.create_texture({TextureFormat::kLuminance, kPaletteSize, 1, TextureWrapMode::kClamp, TextureWrapMode::kClamp}); + + tcb::span map_bytes = tcb::as_bytes(tcb::span(colormap.get(), kPaletteSize)); + rhi.update_texture(ctx, texture, {0, 0, kPaletteSize, 1}, PixelFormat::kR8, map_bytes); + + colormaps_.insert_or_assign(colormap, texture); + return texture; +} + +Handle PaletteManager::find_or_create_extra_lighttable(Rhi& rhi, rhi::Handle ctx, srb2::NotNull lighttable) +{ + if (lighttables_.find(lighttable) != lighttables_.end()) + { + return lighttables_[lighttable]; + } + + Handle texture = rhi.create_texture({TextureFormat::kLuminance, kPaletteSize, kLighttableRows, TextureWrapMode::kClamp, TextureWrapMode::kClamp}); + + tcb::span lighttable_bytes = tcb::as_bytes(tcb::span(lighttable.get(), kPaletteSize * kLighttableRows)); + rhi.update_texture(ctx, texture, {0, 0, kPaletteSize, kLighttableRows}, PixelFormat::kR8, lighttable_bytes); + lighttables_.insert_or_assign(lighttable, texture); + + return texture; +} + +FlatTextureManager::FlatTextureManager() = default; +FlatTextureManager::~FlatTextureManager() = default; + +static uint32_t get_flat_size(lumpnum_t lump) +{ + SRB2_ASSERT(lump != LUMPERROR); + + std::size_t lumplength = W_LumpLength(lump); + if (lumplength == 0) + { + return 0; + } + + if ((lumplength & (lumplength - 1)) != 0) + { + // Lump length is not a power of two and therefore not a flat. + return 0; + } + uint32_t lumpsize = std::pow(2, std::log2(lumplength) / 2); + return lumpsize; +} + +Handle FlatTextureManager::find_or_create_indexed(Rhi& rhi, Handle ctx, lumpnum_t lump) +{ + SRB2_ASSERT(lump != LUMPERROR); + + auto flat_itr = flats_.find(lump); + if (flat_itr != flats_.end()) + { + return flat_itr->second; + } + + uint32_t flat_size = get_flat_size(lump); + Handle new_tex = rhi.create_texture({ + TextureFormat::kLuminanceAlpha, + flat_size, + flat_size, + TextureWrapMode::kRepeat, + TextureWrapMode::kRepeat + }); + flats_.insert({lump, new_tex}); + + std::vector> flat_data; + std::size_t lump_length = W_LumpLength(lump); + flat_data.reserve(flat_size * flat_size); + + const uint8_t* flat_memory = static_cast(W_CacheLumpNum(lump, PU_PATCH)); + SRB2_ASSERT(flat_memory != nullptr); + + tcb::span flat_bytes = tcb::span(flat_memory, lump_length); + for (const uint8_t index : flat_bytes) + { + // The alpha/green channel is set to 0 if it's index 247; this is not usually used but fake floors can be + // masked sometimes, so we need to treat it as transparent when rendering them. + // See https://zdoom.org/wiki/Palette for remarks on fake 247 transparency + flat_data.push_back({index, index == 247 ? static_cast(0) : static_cast(255)}); + } + + // A flat size of 1 would end up being 2 bytes, so we need 2 more bytes to be unpack-aligned on texture upload + // Any other size would implicitly be aligned. + // Sure hope nobody tries to load any flats that are too big for the gpu! + if (flat_size == 1) + { + flat_data.push_back({0, 0}); + } + + tcb::span data_bytes = tcb::as_bytes(tcb::span(flat_data)); + rhi.update_texture(ctx, new_tex, {0, 0, flat_size, flat_size}, rhi::PixelFormat::kRG8, data_bytes); + + return new_tex; +} diff --git a/src/hwr2/resource_management.hpp b/src/hwr2/resource_management.hpp new file mode 100644 index 000000000..328bcd159 --- /dev/null +++ b/src/hwr2/resource_management.hpp @@ -0,0 +1,80 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#ifndef __SRB2_HWR2_RESOURCE_MANAGEMENT_HPP__ +#define __SRB2_HWR2_RESOURCE_MANAGEMENT_HPP__ + +#include "../rhi/rhi.hpp" + +namespace srb2::hwr2 +{ + +class PaletteManager +{ + rhi::Handle palette_; + rhi::Handle lighttable_; + rhi::Handle encore_lighttable_; + rhi::Handle default_colormap_; + + std::unordered_map> colormaps_; + std::unordered_map> lighttables_; + +public: + PaletteManager(); + PaletteManager(const PaletteManager&) = delete; + PaletteManager(PaletteManager&&); + ~PaletteManager(); + PaletteManager& operator=(const PaletteManager&) = delete; + PaletteManager& operator=(PaletteManager&&); + + rhi::Handle palette() const noexcept { return palette_; } + rhi::Handle lighttable() const noexcept { return lighttable_; } + rhi::Handle encore_lighttable() const noexcept { return encore_lighttable_; } + rhi::Handle default_colormap() const noexcept { return default_colormap_; } + + void update(rhi::Rhi& rhi, rhi::Handle ctx); + void destroy_per_frame_resources(rhi::Rhi& rhi); + + rhi::Handle find_or_create_colormap(rhi::Rhi& rhi, rhi::Handle ctx, srb2::NotNull colormap); + rhi::Handle find_or_create_extra_lighttable(rhi::Rhi& rhi, rhi::Handle ctx, srb2::NotNull lighttable); +}; + +/* +A note to the reader: + +RHI/HWR2's architecture is intentionally decoupled in a data-oriented design fashion. Hash map lookups might technically +be slower than storing the RHI handle in a hypothetical Flat class object, but it frees us from worrying about the +validity of a given Handle when the RHI instance changes -- and it _can_, because this is designed to allow multiple +RHI backends -- because any given Pass must be disposed when the RHI changes. The implementation of I_FinishUpdate is +such that if the RHI is not the same as before, all passes must be reconstructed, and so we don't have to worry about +going around and resetting Handle references everywhere. If you're familiar with old GL, it's like decoupling GLmipmap_t +from patch_t. +*/ + +/// @brief Manages textures corresponding to specific flats indexed by lump number. +class FlatTextureManager +{ + std::unordered_map> flats_; + std::vector to_upload_; + std::vector> disposed_textures_; + +public: + FlatTextureManager(); + ~FlatTextureManager(); + + /// @brief Find the indexed texture for a given flat lump, or create one if it doesn't exist yet. Only call this + /// in prepass. + /// @param flat_lump + /// @return + rhi::Handle find_or_create_indexed(rhi::Rhi& rhi, rhi::Handle ctx, lumpnum_t flat_lump); +}; + +} // namespace srb2::hwr2 + +#endif // __SRB2_HWR2_RESOURCE_MANAGEMENT_HPP__ diff --git a/src/hwr2/screen_capture.cpp b/src/hwr2/screen_capture.cpp new file mode 100644 index 000000000..8aed918be --- /dev/null +++ b/src/hwr2/screen_capture.cpp @@ -0,0 +1,59 @@ +// SONIC ROBO BLAST 2 +//----------------------------------------------------------------------------- +// Copyright (C) 2023 by Ronald "Eidolon" Kinard +// +// This program is free software distributed under the +// terms of the GNU General Public License, version 2. +// See the 'LICENSE' file for more details. +//----------------------------------------------------------------------------- + +#include "screen_capture.hpp" + +#include + +#include "../m_misc.h" + +using namespace srb2; +using namespace srb2::hwr2; +using namespace srb2::rhi; + +ScreenshotPass::ScreenshotPass() = default; +ScreenshotPass::~ScreenshotPass() = default; + +void ScreenshotPass::capture(Rhi& rhi, Handle ctx) +{ + bool doing_screenshot = takescreenshot || moviemode != MM_OFF; + + if (!doing_screenshot) + { + return; + } + + pixel_data_.clear(); + packed_data_.clear(); + // Pixel data must be in pack alignment (4) so a stride of non-multiple 4 must align to 4 + uint32_t stride = width_ * 3; + uint32_t read_stride = ((width_ + (kPixelRowPackAlignment - 1)) & ~(kPixelRowPackAlignment - 1)) * 3; + pixel_data_.resize(read_stride * height_); // 3 bytes per pixel for RGB8 + packed_data_.resize(stride * height_); + + tcb::span data_bytes = tcb::as_writable_bytes(tcb::span(pixel_data_)); + rhi.read_pixels(ctx, {0, 0, width_, height_}, PixelFormat::kRGB8, data_bytes); + + for (uint32_t row = 0; row < height_; row++) + { + // Read the aligned data into unaligned linear memory, flipping the rows in the process. + uint32_t pixel_data_row = (height_ - row) - 1; + std::move(&pixel_data_[pixel_data_row * read_stride], &pixel_data_[pixel_data_row * read_stride + stride], &packed_data_[row * stride]); + } + + if (takescreenshot) + { + M_DoScreenShot(width_, height_, tcb::as_bytes(tcb::span(packed_data_))); + } + + if (moviemode != MM_OFF) + { + M_SaveFrame(width_, height_, tcb::as_bytes(tcb::span(packed_data_))); + } +} diff --git a/src/hwr2/pass_screenshot.hpp b/src/hwr2/screen_capture.hpp similarity index 51% rename from src/hwr2/pass_screenshot.hpp rename to src/hwr2/screen_capture.hpp index 36c386a29..4d4411d7f 100644 --- a/src/hwr2/pass_screenshot.hpp +++ b/src/hwr2/screen_capture.hpp @@ -7,38 +7,33 @@ // See the 'LICENSE' file for more details. //----------------------------------------------------------------------------- -#ifndef __SRB2_HWR2_PASS_SCREENSHOT_HPP__ -#define __SRB2_HWR2_PASS_SCREENSHOT_HPP__ +#ifndef __SRB2_HWR2_SCREEN_CAPTURE_HPP__ +#define __SRB2_HWR2_SCREEN_CAPTURE_HPP__ #include #include -#include "pass.hpp" +#include "../rhi/rhi.hpp" namespace srb2::hwr2 { -class ScreenshotPass : public Pass +class ScreenshotPass { - bool doing_screenshot_ = false; - rhi::Handle source_; rhi::Handle render_pass_; std::vector pixel_data_; + std::vector packed_data_; uint32_t width_ = 0; uint32_t height_ = 0; public: ScreenshotPass(); - virtual ~ScreenshotPass(); + ~ScreenshotPass(); - virtual void prepass(rhi::Rhi& rhi) override; - virtual void transfer(rhi::Rhi& rhi, rhi::Handle ctx) override; - virtual void graphics(rhi::Rhi& rhi, rhi::Handle ctx) override; - virtual void postpass(rhi::Rhi& rhi) override; + void capture(rhi::Rhi& rhi, rhi::Handle ctx); - void set_source(rhi::Handle source, uint32_t width, uint32_t height) + void set_source(uint32_t width, uint32_t height) { - source_ = source; width_ = width; height_ = height; } @@ -46,4 +41,4 @@ public: } // namespace srb2::hwr2 -#endif // __SRB2_HWR2_PASS_SCREENSHOT_HPP__ +#endif // __SRB2_HWR2_SCREEN_CAPTURE_HPP__ diff --git a/src/hwr2/pass_software.cpp b/src/hwr2/software_screen_renderer.cpp similarity index 85% rename from src/hwr2/pass_software.cpp rename to src/hwr2/software_screen_renderer.cpp index 1b9cde048..863efb9a5 100644 --- a/src/hwr2/pass_software.cpp +++ b/src/hwr2/software_screen_renderer.cpp @@ -7,7 +7,7 @@ // See the 'LICENSE' file for more details. //----------------------------------------------------------------------------- -#include "pass_software.hpp" +#include "software_screen_renderer.hpp" #include "../i_video.h" #include "../v_video.h" @@ -16,19 +16,11 @@ using namespace srb2; using namespace srb2::hwr2; using namespace srb2::rhi; -SoftwarePass::SoftwarePass() : Pass() +SoftwareScreenRenderer::SoftwareScreenRenderer() = default; +SoftwareScreenRenderer::~SoftwareScreenRenderer() = default; + +void SoftwareScreenRenderer::draw(Rhi& rhi, Handle ctx) { -} - -SoftwarePass::~SoftwarePass() = default; - -void SoftwarePass::prepass(Rhi& rhi) -{ - if (rendermode != render_soft) - { - return; - } - // Render the player views... or not yet? Needs to be moved out of D_Display in d_main.c // Assume it's already been done and vid.buffer contains the composited splitscreen view. // In the future though, we will want to treat each player viewport separately for postprocessing. @@ -75,10 +67,7 @@ void SoftwarePass::prepass(Rhi& rhi) } } } -} -void SoftwarePass::transfer(Rhi& rhi, Handle ctx) -{ // Upload screen tcb::span screen_span; if (width_ % kPixelRowUnpackAlignment > 0) @@ -92,11 +81,3 @@ void SoftwarePass::transfer(Rhi& rhi, Handle ctx) rhi.update_texture(ctx, screen_texture_, {0, 0, width_, height_}, PixelFormat::kR8, screen_span); } - -void SoftwarePass::graphics(Rhi& rhi, Handle ctx) -{ -} - -void SoftwarePass::postpass(Rhi& rhi) -{ -} diff --git a/src/hwr2/pass_software.hpp b/src/hwr2/software_screen_renderer.hpp similarity index 61% rename from src/hwr2/pass_software.hpp rename to src/hwr2/software_screen_renderer.hpp index ae733a816..5c686b17f 100644 --- a/src/hwr2/pass_software.hpp +++ b/src/hwr2/software_screen_renderer.hpp @@ -7,19 +7,19 @@ // See the 'LICENSE' file for more details. //----------------------------------------------------------------------------- -#ifndef __SRB2_HWR2_PASS_SOFTWARE_HPP_ -#define __SRB2_HWR2_PASS_SOFTWARE_HPP_ +#ifndef __SRB2_HWR2_SOFTWARE_SCREEN_RENDERER_HPP_ +#define __SRB2_HWR2_SOFTWARE_SCREEN_RENDERER_HPP_ #include #include -#include "pass.hpp" +#include "../rhi/rhi.hpp" namespace srb2::hwr2 { /// @brief Renders software player views in prepass and uploads the result to a texture in transfer. -class SoftwarePass final : public Pass +class SoftwareScreenRenderer final { rhi::Handle screen_texture_; uint32_t width_ = 0; @@ -30,17 +30,14 @@ class SoftwarePass final : public Pass std::vector copy_buffer_; public: - SoftwarePass(); - virtual ~SoftwarePass(); + SoftwareScreenRenderer(); + ~SoftwareScreenRenderer(); - virtual void prepass(rhi::Rhi& rhi) override; - virtual void transfer(rhi::Rhi& rhi, rhi::Handle ctx) override; - virtual void graphics(rhi::Rhi& rhi, rhi::Handle ctx) override; - virtual void postpass(rhi::Rhi& rhi) override; + void draw(rhi::Rhi& rhi, rhi::Handle ctx); - rhi::Handle screen_texture() const noexcept { return screen_texture_; } + rhi::Handle screen() const { return screen_texture_; } }; } // namespace srb2::hwr2 -#endif // __SRB2_HWR2_PASS_SOFTWARE_HPP_ +#endif // __SRB2_HWR2_SOFTWARE_SCREEN_RENDERER_HPP_ diff --git a/src/hwr2/pass_twodee.cpp b/src/hwr2/twodee_renderer.cpp similarity index 82% rename from src/hwr2/pass_twodee.cpp rename to src/hwr2/twodee_renderer.cpp index 0281737fa..ac9ed0dbd 100644 --- a/src/hwr2/pass_twodee.cpp +++ b/src/hwr2/twodee_renderer.cpp @@ -7,7 +7,7 @@ // See the 'LICENSE' file for more details. //----------------------------------------------------------------------------- -#include "pass_twodee.hpp" +#include "twodee_renderer.hpp" #include @@ -23,23 +23,15 @@ using namespace srb2; using namespace srb2::hwr2; using namespace srb2::rhi; -struct srb2::hwr2::TwodeePassData -{ - Handle default_tex; - std::unordered_map> pipelines; - bool upload_default_tex = false; -}; - -std::shared_ptr srb2::hwr2::make_twodee_pass_data() -{ - return std::make_shared(); -} - -TwodeePass::TwodeePass() : Pass() -{ -} - -TwodeePass::~TwodeePass() = default; +TwodeeRenderer::TwodeeRenderer( + srb2::NotNull palette_manager, + srb2::NotNull flat_manager, + srb2::NotNull patch_atlas_cache +) : palette_manager_(palette_manager), flat_manager_(flat_manager), patch_atlas_cache_(patch_atlas_cache) +{} +TwodeeRenderer::TwodeeRenderer(TwodeeRenderer&&) = default; +TwodeeRenderer::~TwodeeRenderer() = default; +TwodeeRenderer& TwodeeRenderer::operator=(TwodeeRenderer&&) = default; static constexpr const uint32_t kVboInitSize = 32768; static constexpr const uint32_t kIboInitSize = 4096; @@ -123,7 +115,7 @@ static PipelineDesc make_pipeline_desc(TwodeePipelineKey key) {0.f, 0.f, 0.f, 1.f}}; } -void TwodeePass::rewrite_patch_quad_vertices(Draw2dList& list, const Draw2dPatchQuad& cmd) const +void TwodeeRenderer::rewrite_patch_quad_vertices(Draw2dList& list, const Draw2dPatchQuad& cmd) const { // Patch quads are clipped according to the patch's atlas entry const patch_t* patch = cmd.patch; @@ -237,14 +229,8 @@ void TwodeePass::rewrite_patch_quad_vertices(Draw2dList& list, const Draw2dPatch list.vertices[vtx_offs + 3].v = clipped_vmax; } -void TwodeePass::prepass(Rhi& rhi) +void TwodeeRenderer::initialize(Rhi& rhi, Handle ctx) { - if (!ctx_ || !data_) - { - return; - } - - if (data_->pipelines.size() == 0) { TwodeePipelineKey alpha_transparent_tris = {BlendMode::kAlphaTransparent, false}; TwodeePipelineKey modulate_tris = {BlendMode::kModulate, false}; @@ -258,49 +244,45 @@ void TwodeePass::prepass(Rhi& rhi) TwodeePipelineKey subtractive_lines = {BlendMode::kSubtractive, true}; TwodeePipelineKey revsubtractive_lines = {BlendMode::kReverseSubtractive, true}; TwodeePipelineKey invertdest_lines = {BlendMode::kInvertDest, true}; - data_->pipelines.insert({alpha_transparent_tris, rhi.create_pipeline(make_pipeline_desc(alpha_transparent_tris))}); - data_->pipelines.insert({modulate_tris, rhi.create_pipeline(make_pipeline_desc(modulate_tris))}); - data_->pipelines.insert({additive_tris, rhi.create_pipeline(make_pipeline_desc(additive_tris))}); - data_->pipelines.insert({subtractive_tris, rhi.create_pipeline(make_pipeline_desc(subtractive_tris))}); - data_->pipelines.insert({revsubtractive_tris, rhi.create_pipeline(make_pipeline_desc(revsubtractive_tris))}); - data_->pipelines.insert({invertdest_tris, rhi.create_pipeline(make_pipeline_desc(invertdest_tris))}); - data_->pipelines.insert({alpha_transparent_lines, rhi.create_pipeline(make_pipeline_desc(alpha_transparent_lines))}); - data_->pipelines.insert({modulate_lines, rhi.create_pipeline(make_pipeline_desc(modulate_lines))}); - data_->pipelines.insert({additive_lines, rhi.create_pipeline(make_pipeline_desc(additive_lines))}); - data_->pipelines.insert({subtractive_lines, rhi.create_pipeline(make_pipeline_desc(subtractive_lines))}); - data_->pipelines.insert({revsubtractive_lines, rhi.create_pipeline(make_pipeline_desc(revsubtractive_lines))}); - data_->pipelines.insert({invertdest_lines, rhi.create_pipeline(make_pipeline_desc(revsubtractive_lines))}); + pipelines_.insert({alpha_transparent_tris, rhi.create_pipeline(make_pipeline_desc(alpha_transparent_tris))}); + pipelines_.insert({modulate_tris, rhi.create_pipeline(make_pipeline_desc(modulate_tris))}); + pipelines_.insert({additive_tris, rhi.create_pipeline(make_pipeline_desc(additive_tris))}); + pipelines_.insert({subtractive_tris, rhi.create_pipeline(make_pipeline_desc(subtractive_tris))}); + pipelines_.insert({revsubtractive_tris, rhi.create_pipeline(make_pipeline_desc(revsubtractive_tris))}); + pipelines_.insert({invertdest_tris, rhi.create_pipeline(make_pipeline_desc(invertdest_tris))}); + pipelines_.insert({alpha_transparent_lines, rhi.create_pipeline(make_pipeline_desc(alpha_transparent_lines))}); + pipelines_.insert({modulate_lines, rhi.create_pipeline(make_pipeline_desc(modulate_lines))}); + pipelines_.insert({additive_lines, rhi.create_pipeline(make_pipeline_desc(additive_lines))}); + pipelines_.insert({subtractive_lines, rhi.create_pipeline(make_pipeline_desc(subtractive_lines))}); + pipelines_.insert({revsubtractive_lines, rhi.create_pipeline(make_pipeline_desc(revsubtractive_lines))}); + pipelines_.insert({invertdest_lines, rhi.create_pipeline(make_pipeline_desc(revsubtractive_lines))}); } - if (!data_->default_tex) { - data_->default_tex = rhi.create_texture({ + default_tex_ = rhi.create_texture({ TextureFormat::kLuminanceAlpha, 2, 1, TextureWrapMode::kClamp, TextureWrapMode::kClamp }); - data_->upload_default_tex = true; + std::array data = {0, 255, 0, 255}; + rhi.update_texture(ctx, default_tex_, {0, 0, 2, 1}, PixelFormat::kRG8, tcb::as_bytes(tcb::span(data))); } - if (!render_pass_) + + initialized_ = true; +} + +void TwodeeRenderer::flush(Rhi& rhi, Handle ctx, Twodee& twodee) +{ + if (!initialized_) { - render_pass_ = rhi.create_render_pass( - { - false, - AttachmentLoadOp::kLoad, - AttachmentStoreOp::kStore, - AttachmentLoadOp::kDontCare, - AttachmentStoreOp::kDontCare, - AttachmentLoadOp::kDontCare, - AttachmentStoreOp::kDontCare - } - ); + initialize(rhi, ctx); } // Stage 1 - command list patch detection std::unordered_set found_patches; - for (const auto& list : *ctx_) + for (const auto& list : twodee) { for (const auto& cmd : list.cmds) { @@ -313,7 +295,7 @@ void TwodeePass::prepass(Rhi& rhi) } if (cmd.colormap != nullptr) { - palette_manager_->find_or_create_colormap(rhi, cmd.colormap); + palette_manager_->find_or_create_colormap(rhi, ctx, cmd.colormap); } }, [&](const Draw2dVertices& cmd) {}}; @@ -325,10 +307,10 @@ void TwodeePass::prepass(Rhi& rhi) { patch_atlas_cache_->queue_patch(patch); } - patch_atlas_cache_->pack(rhi); + patch_atlas_cache_->pack(rhi, ctx); size_t list_index = 0; - for (auto& list : *ctx_) + for (auto& list : twodee) { Handle vbo; uint32_t vertex_data_size = tcb::as_bytes(tcb::span(list.vertices)).size(); @@ -461,7 +443,7 @@ void TwodeePass::prepass(Rhi& rhi) { if (cmd.flat_lump != LUMPERROR) { - flat_manager_->find_or_create_indexed(rhi, cmd.flat_lump); + flat_manager_->find_or_create_indexed(rhi, ctx, cmd.flat_lump); typeof(the_new_one.texture) t = MergedTwodeeCommandFlatTexture {cmd.flat_lump}; the_new_one.texture = t; } @@ -496,28 +478,12 @@ void TwodeePass::prepass(Rhi& rhi) list_index++; } -} - -void TwodeePass::transfer(Rhi& rhi, Handle ctx) -{ - if (!ctx_ || !data_) - { - return; - } - - if (data_->upload_default_tex) - { - std::array data = {0, 255, 0, 255}; - rhi.update_texture(ctx, data_->default_tex, {0, 0, 2, 1}, PixelFormat::kRG8, tcb::as_bytes(tcb::span(data))); - - data_->upload_default_tex = false; - } Handle palette_tex = palette_manager_->palette(); // Update the buffers for each list - auto ctx_list_itr = ctx_->begin(); - for (size_t i = 0; i < cmd_lists_.size() && ctx_list_itr != ctx_->end(); i++) + auto ctx_list_itr = twodee.begin(); + for (size_t i = 0; i < cmd_lists_.size() && ctx_list_itr != twodee.end(); i++) { auto& merged_list = cmd_lists_[i]; auto& orig_list = *ctx_list_itr; @@ -540,7 +506,7 @@ void TwodeePass::transfer(Rhi& rhi, Handle ctx) }, [&](const MergedTwodeeCommandFlatTexture& tex) { - Handle th = flat_manager_->find_indexed(tex.lump); + Handle th = flat_manager_->find_or_create_indexed(rhi, ctx, tex.lump); SRB2_ASSERT(th != kNullHandle); tx[0] = {SamplerName::kSampler0, th}; tx[1] = {SamplerName::kSampler1, palette_tex}; @@ -551,7 +517,7 @@ void TwodeePass::transfer(Rhi& rhi, Handle ctx) } else { - tx[0] = {SamplerName::kSampler0, data_->default_tex}; + tx[0] = {SamplerName::kSampler0, default_tex_}; tx[1] = {SamplerName::kSampler1, palette_tex}; } @@ -559,12 +525,12 @@ void TwodeePass::transfer(Rhi& rhi, Handle ctx) Handle colormap_h = palette_manager_->default_colormap(); if (colormap) { - colormap_h = palette_manager_->find_colormap(colormap); + colormap_h = palette_manager_->find_or_create_colormap(rhi, ctx, colormap); SRB2_ASSERT(colormap_h != kNullHandle); } tx[2] = {SamplerName::kSampler2, colormap_h}; mcmd.binding_set = - rhi.create_binding_set(ctx, data_->pipelines[mcmd.pipeline_key], {tcb::span(vbos), tcb::span(tx)}); + rhi.create_binding_set(ctx, pipelines_[mcmd.pipeline_key], {tcb::span(vbos), tcb::span(tx)}); } ctx_list_itr++; @@ -588,28 +554,10 @@ void TwodeePass::transfer(Rhi& rhi, Handle ctx) // Sampler 0 Is Indexed Alpha (yes, it always is) static_cast(1) }; - us_1 = rhi.create_uniform_set(ctx, {tcb::span(g1_uniforms)}); - us_2 = rhi.create_uniform_set(ctx, {tcb::span(g2_uniforms)}); -} - -static constexpr const glm::vec4 kClearColor = {0, 0, 0, 1}; - -void TwodeePass::graphics(Rhi& rhi, Handle ctx) -{ - if (!ctx_ || !data_) - { - return; - } - - if (output_) - { - rhi.begin_render_pass(ctx, {render_pass_, output_, std::nullopt, kClearColor}); - } - else - { - rhi.begin_default_render_pass(ctx, false); - } + Handle us_1 = rhi.create_uniform_set(ctx, {tcb::span(g1_uniforms)}); + Handle us_2 = rhi.create_uniform_set(ctx, {tcb::span(g2_uniforms)}); + // Presumably, we're already in a renderpass when flush is called for (auto& list : cmd_lists_) { for (auto& cmd : list.cmds) @@ -620,13 +568,10 @@ void TwodeePass::graphics(Rhi& rhi, Handle ctx) // This shouldn't happen, but, just in case... continue; } - SRB2_ASSERT(data_->pipelines.find(cmd.pipeline_key) != data_->pipelines.end()); - Handle pl = data_->pipelines[cmd.pipeline_key]; + SRB2_ASSERT(pipelines_.find(cmd.pipeline_key) != pipelines_.end()); + Handle pl = pipelines_[cmd.pipeline_key]; rhi.bind_pipeline(ctx, pl); - if (output_) - { - rhi.set_viewport(ctx, {0, 0, output_width_, output_height_}); - } + rhi.set_viewport(ctx, {0, 0, static_cast(vid.width), static_cast(vid.height)}); rhi.bind_uniform_set(ctx, 0, us_1); rhi.bind_uniform_set(ctx, 1, us_2); rhi.bind_binding_set(ctx, cmd.binding_set); @@ -634,15 +579,15 @@ void TwodeePass::graphics(Rhi& rhi, Handle ctx) rhi.draw_indexed(ctx, cmd.elements, cmd.index_offset); } } - rhi.end_render_pass(ctx); -} - -void TwodeePass::postpass(Rhi& rhi) -{ - if (!ctx_ || !data_) - { - return; - } cmd_lists_.clear(); + + // Reset context for next drawing batch + twodee = Twodee(); + + // Reset the patch atlas if needed + if (patch_atlas_cache_->need_to_reset()) + { + patch_atlas_cache_->reset(rhi); + } } diff --git a/src/hwr2/pass_twodee.hpp b/src/hwr2/twodee_renderer.hpp similarity index 70% rename from src/hwr2/pass_twodee.hpp rename to src/hwr2/twodee_renderer.hpp index da5c2f66f..fe674c009 100644 --- a/src/hwr2/pass_twodee.hpp +++ b/src/hwr2/twodee_renderer.hpp @@ -19,17 +19,13 @@ #include "../cxxutil.hpp" #include "patch_atlas.hpp" -#include "pass.hpp" -#include "pass_resource_managers.hpp" +#include "resource_management.hpp" #include "twodee.hpp" namespace srb2::hwr2 { -class TwodeePass; - -/// @brief Shared structures to allow multiple 2D instances to share the same atlases -struct TwodeePassData; +class TwodeeRenderer; /// @brief Hash map key for caching pipelines struct TwodeePipelineKey @@ -41,6 +37,22 @@ struct TwodeePipelineKey bool operator!=(const TwodeePipelineKey& r) const noexcept { return !(*this == r); } }; +} // namespace srb2::hwr2 + +template <> +struct std::hash +{ + std::size_t operator()(const srb2::hwr2::TwodeePipelineKey& v) const + { + std::size_t hash = 0; + srb2::hash_combine(hash, v.blend, v.lines); + return hash; + } +}; + +namespace srb2::hwr2 +{ + struct MergedTwodeeCommandFlatTexture { lumpnum_t lump; @@ -69,52 +81,44 @@ struct MergedTwodeeCommandList std::vector cmds; }; -std::shared_ptr make_twodee_pass_data(); - -struct TwodeePass final : public Pass +class TwodeeRenderer final { - Twodee* ctx_ = nullptr; + bool initialized_ = false; std::variant, rhi::Handle> out_color_; - std::shared_ptr data_; - std::shared_ptr palette_manager_; - std::shared_ptr flat_manager_; - std::shared_ptr patch_atlas_cache_; - rhi::Handle us_1; - rhi::Handle us_2; + PaletteManager* palette_manager_; + FlatTextureManager* flat_manager_; + PatchAtlasCache* patch_atlas_cache_; std::vector cmd_lists_; std::vector, std::size_t>> vbos_; std::vector, std::size_t>> ibos_; rhi::Handle render_pass_; rhi::Handle output_; - uint32_t output_width_ = 0; - uint32_t output_height_ = 0; + rhi::Handle default_tex_; + std::unordered_map> pipelines_; void rewrite_patch_quad_vertices(Draw2dList& list, const Draw2dPatchQuad& cmd) const; - TwodeePass(); - virtual ~TwodeePass(); + void initialize(rhi::Rhi& rhi, rhi::Handle ctx); - virtual void prepass(rhi::Rhi& rhi) override; +public: + TwodeeRenderer( + srb2::NotNull palette_manager, + srb2::NotNull flat_manager, + srb2::NotNull patch_atlas_cache + ); + TwodeeRenderer(const TwodeeRenderer&) = delete; + TwodeeRenderer(TwodeeRenderer&&); + ~TwodeeRenderer(); + TwodeeRenderer& operator=(const TwodeeRenderer&) = delete; + TwodeeRenderer& operator=(TwodeeRenderer&&); - virtual void transfer(rhi::Rhi& rhi, rhi::Handle ctx) override; - - virtual void graphics(rhi::Rhi& rhi, rhi::Handle ctx) override; - - virtual void postpass(rhi::Rhi& rhi) override; + /// @brief Flush accumulated Twodee state and perform draws. + /// @param rhi + /// @param ctx + void flush(rhi::Rhi& rhi, rhi::Handle ctx, Twodee& twodee); }; } // namespace srb2::hwr2 -template <> -struct std::hash -{ - std::size_t operator()(const srb2::hwr2::TwodeePipelineKey& v) const - { - std::size_t hash = 0; - srb2::hash_combine(hash, v.blend, v.lines); - return hash; - } -}; - #endif // __SRB2_HWR2_PASS_TWODEE_HPP__ diff --git a/src/i_video.h b/src/i_video.h index 4da72efc0..cd9cbecd2 100644 --- a/src/i_video.h +++ b/src/i_video.h @@ -18,6 +18,7 @@ #ifdef __cplusplus +#include "hwr2/hardware_state.hpp" #include "rhi/rhi.hpp" namespace srb2::sys { @@ -26,7 +27,10 @@ extern rhi::Handle g_current_rhi; rhi::Rhi* get_rhi(rhi::Handle handle); -} // namespace +rhi::Handle main_graphics_context(); +hwr2::HardwareState* main_hardware_state(); + +} // namespace srb2::sys extern "C" { #endif @@ -130,25 +134,14 @@ extern boolean allow_fullscreen; */ void I_UpdateNoBlit(void); -/** \brief Begin a new Twodee frame. +/** \brief Start a display update. */ -void I_NewTwodeeFrame(void); - -/** \brief Begin a new dear imgui frame. -*/ -void I_NewImguiFrame(void); +void I_StartDisplayUpdate(void); /** \brief Update video system with updating frame */ void I_FinishUpdate(void); -void I_FinishUpdateWipeStartScreen(void); -void I_FinishUpdateWipeEndScreen(void); - -/** \brief Update video system during a wipe -*/ -void I_FinishUpdateWipe(void); - /** \brief I_FinishUpdate(), but vsync disabled */ void I_UpdateNoVsync(void); @@ -179,6 +172,8 @@ void I_EndRead(void); UINT32 I_GetRefreshRate(void); +void I_CaptureVideoFrame(void); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/i_video_common.cpp b/src/i_video_common.cpp index 20294e9b6..4a493843c 100644 --- a/src/i_video_common.cpp +++ b/src/i_video_common.cpp @@ -17,16 +17,9 @@ #include "cxxutil.hpp" #include "f_finale.h" +#include "m_misc.h" +#include "hwr2/hardware_state.hpp" #include "hwr2/patch_atlas.hpp" -#include "hwr2/pass_blit_postimg_screens.hpp" -#include "hwr2/pass_blit_rect.hpp" -#include "hwr2/pass_imgui.hpp" -#include "hwr2/pass_manager.hpp" -#include "hwr2/pass_postprocess.hpp" -#include "hwr2/pass_resource_managers.hpp" -#include "hwr2/pass_screenshot.hpp" -#include "hwr2/pass_software.hpp" -#include "hwr2/pass_twodee.hpp" #include "hwr2/twodee.hpp" #include "v_video.h" @@ -56,21 +49,10 @@ using namespace srb2; using namespace srb2::hwr2; using namespace srb2::rhi; -namespace -{ -struct InternalPassData -{ - std::shared_ptr resource_passmanager; - std::shared_ptr normal_rendering; - std::shared_ptr wipe_capture_start_rendering; - std::shared_ptr wipe_capture_end_rendering; - std::shared_ptr wipe_rendering; -}; -} // namespace - -static std::unique_ptr g_passes; static Rhi* g_last_known_rhi = nullptr; static bool g_imgui_frame_active = false; +static Handle g_main_graphics_context; +static HardwareState g_hw_state; Handle srb2::sys::g_current_rhi = kNullHandle; @@ -79,6 +61,46 @@ static bool rhi_changed(Rhi* rhi) return g_last_known_rhi != rhi; } +static void reset_hardware_state(Rhi* rhi) +{ + // The lifetime of objects pointed to by RHI Handles is determined by the RHI itself, so it is enough to simply + // "forget" about the resources previously known. + g_hw_state = HardwareState {}; + g_hw_state.palette_manager = std::make_unique(); + g_hw_state.flat_manager = std::make_unique(); + g_hw_state.patch_atlas_cache = std::make_unique(2048, 3); + g_hw_state.twodee_renderer = std::make_unique( + g_hw_state.palette_manager.get(), + g_hw_state.flat_manager.get(), + g_hw_state.patch_atlas_cache.get() + ); + g_hw_state.software_screen_renderer = std::make_unique(); + g_hw_state.blit_postimg_screens = std::make_unique(g_hw_state.palette_manager.get()); + g_hw_state.wipe = std::make_unique(); + g_hw_state.blit_rect = std::make_unique(); + g_hw_state.screen_capture = std::make_unique(); + g_hw_state.wipe_frames = {}; + + g_last_known_rhi = rhi; +} + +static void new_twodee_frame(); +static void new_imgui_frame(); + +static void preframe_update(Rhi& rhi) +{ + SRB2_ASSERT(g_main_graphics_context != kNullHandle); + + g_hw_state.palette_manager->update(rhi, g_main_graphics_context); + new_twodee_frame(); + new_imgui_frame(); +} + +static void postframe_update(Rhi& rhi) +{ + g_hw_state.palette_manager->destroy_per_frame_resources(rhi); +} + #ifdef HWRENDER static void finish_legacy_ogl_update() { @@ -187,278 +209,13 @@ static void temp_legacy_finishupdate_draws() ST_drawDebugInfo(); } -static InternalPassData build_pass_manager() -{ - auto framebuffer_manager = std::make_shared(); - auto palette_manager = std::make_shared(); - auto common_resources_manager = std::make_shared(); - auto flat_texture_manager = std::make_shared(); - auto patch_atlas_cache = std::make_shared(2048, 2); - auto resource_manager = std::make_shared(); - - resource_manager->insert("framebuffer_manager", framebuffer_manager); - resource_manager->insert("palette_manager", palette_manager); - resource_manager->insert("common_resources_manager", common_resources_manager); - resource_manager->insert("flat_texture_manager", flat_texture_manager); - resource_manager->insert("patch_atlas_cache", patch_atlas_cache); - - // Basic Rendering is responsible for drawing 3d, 2d, and postprocessing the image. - // This is drawn to an alternating internal color buffer. - // Normal Rendering will output the result via final composite and present. - // Wipe Start Screen and Wipe End Screen will save to special color buffers used for Wipe Rendering. - auto basic_rendering = std::make_shared(); - - auto software_pass = std::make_shared(); - auto blit_postimg_screens = std::make_shared(palette_manager); - auto twodee = std::make_shared(); - twodee->flat_manager_ = flat_texture_manager; - twodee->patch_atlas_cache_ = patch_atlas_cache; - twodee->data_ = make_twodee_pass_data(); - twodee->ctx_ = &g_2d; - auto pp_simple_blit_pass = std::make_shared(false); - auto screenshot_pass = std::make_shared(); - auto imgui_pass = std::make_shared(); - auto final_composite_pass = std::make_shared(true); - - basic_rendering->insert( - "3d_prepare", - [framebuffer_manager](PassManager& mgr, Rhi&) - { - const bool sw_enabled = rendermode == render_soft && gamestate != GS_NULL; - - mgr.set_pass_enabled("software", sw_enabled); - mgr.set_pass_enabled("blit_postimg_screens_prepare", sw_enabled); - mgr.set_pass_enabled("blit_postimg_screens", sw_enabled && !g_wipeskiprender); - } - ); - basic_rendering->insert("software", software_pass); - basic_rendering->insert( - "blit_postimg_screens_prepare", - [blit_postimg_screens, software_pass, framebuffer_manager](PassManager&, Rhi&) - { - const bool sw_enabled = rendermode == render_soft && gamestate != GS_NULL; - const int screens = std::clamp(r_splitscreen + 1, 1, MAXSPLITSCREENPLAYERS); - - blit_postimg_screens->set_num_screens(screens); - for (int i = 0; i < screens; i++) - { - if (sw_enabled) - { - glm::vec2 uv_offset {0.f, 0.f}; - glm::vec2 uv_size {1.f, 1.f}; - - if (screens > 2) - { - uv_size = glm::vec2(.5f, .5f); - switch (i) - { - case 0: - uv_offset = glm::vec2(0.f, 0.f); - break; - case 1: - uv_offset = glm::vec2(.5f, 0.f); - break; - case 2: - uv_offset = glm::vec2(0.f, .5f); - break; - case 3: - uv_offset = glm::vec2(.5f, .5f); - break; - } - } - else if (screens > 1) - { - uv_size = glm::vec2(1.0, 0.5); - if (i == 1) - { - uv_offset = glm::vec2(0.f, .5f); - } - } - - // "You should probably never have more than 3 levels of indentation" -- Eidolon, the author of this - - blit_postimg_screens->set_screen( - i, - { - software_pass->screen_texture(), - true, - uv_offset, - uv_size, - { - postimgtype[i] == postimg_water, - postimgtype[i] == postimg_heat, - postimgtype[i] == postimg_flip, - postimgtype[i] == postimg_mirror - } - } - ); - } - } - blit_postimg_screens->set_target(framebuffer_manager->main_color(), vid.width, vid.height); - } - ); - basic_rendering->insert("blit_postimg_screens", blit_postimg_screens); - - basic_rendering->insert( - "2d_prepare", - [twodee, framebuffer_manager, palette_manager](PassManager& mgr, Rhi&) - { - twodee->output_ = framebuffer_manager->main_color(); - twodee->palette_manager_ = palette_manager; - twodee->output_width_ = vid.width; - twodee->output_height_ = vid.height; - } - ); - basic_rendering->insert("2d", twodee); - - basic_rendering->insert( - "pp_final_simple_blit_prepare", - [pp_simple_blit_pass, framebuffer_manager](PassManager&, Rhi&) - { - framebuffer_manager->swap_post(); - pp_simple_blit_pass->set_texture(framebuffer_manager->main_color(), vid.width, vid.height); - pp_simple_blit_pass - ->set_output(framebuffer_manager->current_post_color(), vid.width, vid.height, false, false); - } - ); - basic_rendering->insert("pp_final_simple_blit", pp_simple_blit_pass); - - auto screenshot_rendering = std::make_shared(); - - screenshot_rendering->insert( - "screenshot_prepare", - [screenshot_pass, framebuffer_manager](PassManager&, Rhi&) - { - screenshot_pass->set_source(framebuffer_manager->current_post_color(), vid.width, vid.height); - } - ); - screenshot_rendering->insert("screenshot", screenshot_pass); - - // Composite-present takes the current postprocess result and outputs it to the default framebuffer. - // It also renders imgui and presents the screen. - auto composite_present_rendering = std::make_shared(); - - composite_present_rendering->insert( - "final_composite_prepare", - [final_composite_pass, framebuffer_manager](PassManager&, Rhi&) - { - final_composite_pass->set_texture(framebuffer_manager->current_post_color(), vid.width, vid.height); - final_composite_pass->set_output(kNullHandle, vid.realwidth, vid.realheight, true, false); - } - ); - composite_present_rendering->insert("final_composite", final_composite_pass); - composite_present_rendering->insert("imgui", imgui_pass); - composite_present_rendering->insert( - "present", - [](PassManager&, Rhi& rhi) {}, - [framebuffer_manager](PassManager&, Rhi& rhi) - { - g_imgui_frame_active = false; - rhi.present(); - rhi.finish(); - framebuffer_manager->reset_post(); - I_NewImguiFrame(); - } - ); - - // Normal rendering combines basic rendering and composite-present. - auto normal_rendering = std::make_shared(); - - normal_rendering->insert("resource_manager", resource_manager); - normal_rendering->insert("basic_rendering", basic_rendering); - normal_rendering->insert("screenshot_rendering", screenshot_rendering); - normal_rendering->insert("composite_present_rendering", composite_present_rendering); - - // Wipe Start Screen Capture rendering - auto wipe_capture_start_rendering = std::make_shared(); - auto wipe_start_blit = std::make_shared(); - - wipe_capture_start_rendering->insert("resource_manager", resource_manager); - wipe_capture_start_rendering->insert("basic_rendering", basic_rendering); - wipe_capture_start_rendering->insert( - "wipe_capture_prepare", - [framebuffer_manager, wipe_start_blit](PassManager&, Rhi&) - { - wipe_start_blit->set_texture(framebuffer_manager->previous_post_color(), vid.width, vid.height); - wipe_start_blit->set_output(framebuffer_manager->wipe_start_color(), vid.width, vid.height, false, true); - } - ); - wipe_capture_start_rendering->insert("wipe_capture", wipe_start_blit); - - // Wipe End Screen Capture rendering - auto wipe_capture_end_rendering = std::make_shared(); - auto wipe_end_blit = std::make_shared(); - auto wipe_end_blit_start_to_main = std::make_shared(); - - wipe_capture_end_rendering->insert("resource_manager", resource_manager); - wipe_capture_end_rendering->insert("basic_rendering", basic_rendering); - wipe_capture_end_rendering->insert( - "wipe_capture_prepare", - [framebuffer_manager, wipe_end_blit, wipe_end_blit_start_to_main](PassManager&, Rhi&) - { - wipe_end_blit->set_texture(framebuffer_manager->current_post_color(), vid.width, vid.height); - wipe_end_blit->set_output(framebuffer_manager->wipe_end_color(), vid.width, vid.height, false, true); - - wipe_end_blit_start_to_main->set_texture( - framebuffer_manager->wipe_start_color(), - vid.width, - vid.height - ); - wipe_end_blit_start_to_main->set_output( - framebuffer_manager->main_color(), - vid.width, - vid.height, - false, - true - ); - } - ); - wipe_capture_end_rendering->insert("wipe_capture", wipe_end_blit); - wipe_capture_end_rendering->insert("wipe_end_blit_start_to_main", wipe_end_blit_start_to_main); - - // Wipe rendering only runs the wipe shader on the start and end screens, and adds composite-present. - auto wipe_rendering = std::make_shared(); - - auto pp_wipe_pass = std::make_shared(); - - wipe_rendering->insert("resource_manager", resource_manager); - wipe_rendering->insert( - "pp_final_wipe_prepare", - [pp_wipe_pass, framebuffer_manager, common_resources_manager](PassManager&, Rhi&) - { - framebuffer_manager->swap_post(); - Handle start = framebuffer_manager->main_color(); - Handle end = framebuffer_manager->wipe_end_color(); - if (g_wipereverse) - { - std::swap(start, end); - } - pp_wipe_pass->set_start(start); - pp_wipe_pass->set_end(end); - pp_wipe_pass->set_target(framebuffer_manager->current_post_color(), vid.width, vid.height); - } - ); - wipe_rendering->insert("pp_final_wipe", pp_wipe_pass); - wipe_rendering->insert("screenshot_rendering", screenshot_rendering); - wipe_rendering->insert("composite_present_rendering", composite_present_rendering); - - InternalPassData ret; - ret.resource_passmanager = resource_manager; - ret.normal_rendering = normal_rendering; - ret.wipe_capture_start_rendering = wipe_capture_start_rendering; - ret.wipe_capture_end_rendering = wipe_capture_end_rendering; - ret.wipe_rendering = wipe_rendering; - - return ret; -} - -void I_NewTwodeeFrame(void) +static void new_twodee_frame() { g_2d = Twodee(); Patch_ResetFreedThisFrame(); } -void I_NewImguiFrame(void) +static void new_imgui_frame() { if (g_imgui_frame_active) { @@ -472,13 +229,61 @@ void I_NewImguiFrame(void) g_imgui_frame_active = true; } -static void maybe_reinit_passes(Rhi* rhi) +rhi::Handle sys::main_graphics_context() { - if (rhi_changed(rhi) || !g_passes) + return g_main_graphics_context; +} + +HardwareState* sys::main_hardware_state() +{ + return &g_hw_state; +} + +void I_CaptureVideoFrame() +{ + rhi::Rhi* rhi = srb2::sys::get_rhi(srb2::sys::g_current_rhi); + rhi::Handle ctx = srb2::sys::main_graphics_context(); + hwr2::HardwareState* hw_state = srb2::sys::main_hardware_state(); + + hw_state->screen_capture->set_source(static_cast(vid.width), static_cast(vid.height)); + hw_state->screen_capture->capture(*rhi, ctx); +} + +void I_StartDisplayUpdate(void) +{ + if (rendermode == render_none) { - g_last_known_rhi = rhi; - g_passes = std::make_unique(build_pass_manager()); + return; } + +#ifdef HWRENDER + if (rendermode == render_opengl) + { + return; + } +#endif + + rhi::Rhi* rhi = sys::get_rhi(sys::g_current_rhi); + + if (rhi == nullptr) + { + // ??? + return; + } + + if (rhi_changed(rhi)) + { + // Reset all hardware 2 state + reset_hardware_state(rhi); + } + + rhi::Handle ctx = rhi->begin_graphics(); + + rhi->begin_default_render_pass(ctx, false); + + g_main_graphics_context = ctx; + + preframe_update(*rhi); } void I_FinishUpdate(void) @@ -506,99 +311,22 @@ void I_FinishUpdate(void) return; } - maybe_reinit_passes(rhi); + rhi::Handle ctx = g_main_graphics_context; - g_passes->normal_rendering->render(*rhi); -} - -void I_FinishUpdateWipeStartScreen(void) -{ - if (rendermode == render_none) - { - return; - } - -#ifdef HWRENDER - if (rendermode == render_opengl) - { - finish_legacy_ogl_update(); - return; - } -#endif - - temp_legacy_finishupdate_draws(); - - rhi::Rhi* rhi = sys::get_rhi(sys::g_current_rhi); - - if (rhi == nullptr) - { - // ??? - return; - } - - maybe_reinit_passes(rhi); - - g_passes->wipe_capture_start_rendering->render(*rhi); - I_NewImguiFrame(); -} - -void I_FinishUpdateWipeEndScreen(void) -{ - if (rendermode == render_none) - { - return; - } - -#ifdef HWRENDER - if (rendermode == render_opengl) - { - finish_legacy_ogl_update(); - return; - } -#endif - - temp_legacy_finishupdate_draws(); - - rhi::Rhi* rhi = sys::get_rhi(sys::g_current_rhi); - - if (rhi == nullptr) - { - // ??? - return; - } - - maybe_reinit_passes(rhi); - - g_passes->wipe_capture_end_rendering->render(*rhi); - I_NewImguiFrame(); -} - -void I_FinishUpdateWipe(void) -{ - if (rendermode == render_none) - { - return; - } - -#ifdef HWRENDER - if (rendermode == render_opengl) - { - finish_legacy_ogl_update(); - return; - } -#endif - - temp_legacy_finishupdate_draws(); - - rhi::Rhi* rhi = sys::get_rhi(sys::g_current_rhi); - - if (rhi == nullptr) - { - // ??? - return; - } - - maybe_reinit_passes(rhi); - - g_passes->wipe_rendering->render(*rhi); + if (ctx != kNullHandle) + { + // better hope the drawing code left the context in a render pass, I guess + g_hw_state.twodee_renderer->flush(*rhi, ctx, g_2d); + rhi->end_render_pass(ctx); + rhi->end_graphics(ctx); + g_main_graphics_context = kNullHandle; + + postframe_update(*rhi); + } + + rhi->present(); + rhi->finish(); + + // Immediately prepare to begin drawing the next frame + I_StartDisplayUpdate(); } diff --git a/src/k_vote.c b/src/k_vote.c index 730300dd5..731a6cd8b 100644 --- a/src/k_vote.c +++ b/src/k_vote.c @@ -796,9 +796,6 @@ void Y_VoteDrawer(void) { static angle_t rubyFloatTime = 0; - // If we early return, skip drawing the 3D scene (software buffer) so it doesn't clobber the frame for the wipe - g_wipeskiprender = true; - if (rendermode == render_none) { return; @@ -814,8 +811,6 @@ void Y_VoteDrawer(void) return; } - g_wipeskiprender = false; - vote_draw.ruby_height = FINESINE(rubyFloatTime >> ANGLETOFINESHIFT); rubyFloatTime += FixedMul(ANGLE_MAX / NEWTICRATE, renderdeltatics); @@ -1226,7 +1221,7 @@ static void Y_TryMapAngerVote(void) INT32 numPlayers = 0; INT32 i = 0; - + for (i = 0; i < MAXPLAYERS; i++) { if (Y_PlayerIDCanVote(i) == false) diff --git a/src/p_setup.c b/src/p_setup.c index 867887c9d..67e70385e 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -8074,14 +8074,9 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) // This is needed. Don't touch. maptol = mapheaderinfo[gamemap-1]->typeoflevel; - // HWR2 skip 3d render draw hack to avoid losing the current wipe screen - g_wipeskiprender = true; - CON_Drawer(); // let the user know what we are going to do I_FinishUpdate(); // page flip or blit buffer - g_wipeskiprender = false; - // Reset the palette if (rendermode != render_none) V_SetPaletteLump("PLAYPAL"); @@ -8150,6 +8145,8 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) lastwipetic = nowtime; \ if (moviemode && rendermode == render_opengl) \ M_LegacySaveFrame(); \ + else if (moviemode && rendermode != render_none) \ + I_CaptureVideoFrame(); \ NetKeepAlive(); \ } \ @@ -8451,7 +8448,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate) } // Now safe to free. - // We do the following silly + // We do the following silly // construction because vres_Free // no-sells deletions of pointers // that are == curmapvirt. diff --git a/src/rhi/gl3_core/gl3_core_rhi.cpp b/src/rhi/gl3_core/gl3_core_rhi.cpp index b8144788f..55f856699 100644 --- a/src/rhi/gl3_core/gl3_core_rhi.cpp +++ b/src/rhi/gl3_core/gl3_core_rhi.cpp @@ -27,12 +27,17 @@ using namespace rhi; #ifndef NDEBUG #define GL_ASSERT \ + while (1) \ { \ GLenum __err = gl_->GetError(); \ if (__err != GL_NO_ERROR) \ { \ I_Error("GL Error at %s %d: %d", __FILE__, __LINE__, __err); \ } \ + else \ + { \ + break; \ + } \ } #else #define GL_ASSERT ; @@ -1193,6 +1198,10 @@ void GlCoreRhi::end_graphics(rhi::Handle handle) SRB2_ASSERT(graphics_context_active_ == true); SRB2_ASSERT(current_pipeline_.has_value() == false && current_render_pass_.has_value() == false); graphics_context_generation_ += 1; + if (graphics_context_generation_ == 0) + { + graphics_context_generation_ = 1; + } graphics_context_active_ = false; gl_->Flush(); GL_ASSERT; @@ -1646,6 +1655,7 @@ void GlCoreRhi::set_viewport(Handle ctx, const Rect& rect) SRB2_ASSERT(current_render_pass_.has_value() == true && current_pipeline_.has_value() == true); gl_->Viewport(rect.x, rect.y, rect.w, rect.h); + GL_ASSERT; } void GlCoreRhi::draw(Handle ctx, uint32_t vertex_count, uint32_t first_vertex) @@ -1688,9 +1698,38 @@ void GlCoreRhi::read_pixels(Handle ctx, const Rect& rect, Pixel GLenum type = std::get<1>(gl_format); GLint size = std::get<2>(gl_format); - SRB2_ASSERT(out.size_bytes() == rect.w * rect.h * size); + // Pack alignment comes into play. + uint32_t pack_aligned_w = (rect.w + (kPixelRowPackAlignment - 1)) & ~(kPixelRowPackAlignment - 1); + + SRB2_ASSERT(out.size_bytes() == pack_aligned_w * rect.h * size); + + bool is_back; + Rect src_dim; + auto render_pass_visitor = srb2::Overload { + [&](const DefaultRenderPassState& state) { + is_back = true; + src_dim = platform_->get_default_framebuffer_dimensions(); + }, + [&](const RenderPassBeginInfo& state) { + is_back = false; + SRB2_ASSERT(texture_slab_.is_valid(state.color_attachment)); + auto& attach_tex = texture_slab_[state.color_attachment]; + src_dim = {0, 0, attach_tex.desc.width, attach_tex.desc.height}; + } + }; + std::visit(render_pass_visitor, *current_render_pass_); + + SRB2_ASSERT(rect.x >= 0); + SRB2_ASSERT(rect.y >= 0); + SRB2_ASSERT(rect.x + rect.w <= src_dim.w); + SRB2_ASSERT(rect.y + rect.h <= src_dim.h); + + GLenum read_buffer = is_back ? GL_BACK_LEFT : GL_COLOR_ATTACHMENT0; + gl_->ReadBuffer(read_buffer); + GL_ASSERT; gl_->ReadPixels(rect.x, rect.y, rect.w, rect.h, layout, type, out.data()); + GL_ASSERT; } void GlCoreRhi::set_stencil_reference(Handle ctx, CullMode face, uint8_t reference) @@ -1839,3 +1878,53 @@ void GlCoreRhi::finish() disposal_.clear(); GL_ASSERT; } + +void GlCoreRhi::copy_framebuffer_to_texture( + Handle ctx, + Handle dst_tex, + const Rect& dst_region, + const Rect& src_region +) +{ + SRB2_ASSERT(graphics_context_active_ == true); + SRB2_ASSERT(current_render_pass_.has_value()); + SRB2_ASSERT(texture_slab_.is_valid(dst_tex)); + + auto& tex = texture_slab_[dst_tex]; + SRB2_ASSERT(dst_region.w == src_region.w); + SRB2_ASSERT(dst_region.h == src_region.h); + SRB2_ASSERT(dst_region.x >= 0); + SRB2_ASSERT(dst_region.y >= 0); + SRB2_ASSERT(dst_region.x + dst_region.w <= tex.desc.width); + SRB2_ASSERT(dst_region.y + dst_region.h <= tex.desc.height); + + bool is_back; + Rect src_dim; + auto render_pass_visitor = srb2::Overload { + [&](const DefaultRenderPassState& state) { + is_back = true; + src_dim = platform_->get_default_framebuffer_dimensions(); + }, + [&](const RenderPassBeginInfo& state) { + is_back = false; + SRB2_ASSERT(texture_slab_.is_valid(state.color_attachment)); + auto& attach_tex = texture_slab_[state.color_attachment]; + src_dim = {0, 0, attach_tex.desc.width, attach_tex.desc.height}; + } + }; + std::visit(render_pass_visitor, *current_render_pass_); + + SRB2_ASSERT(src_region.x >= 0); + SRB2_ASSERT(src_region.y >= 0); + SRB2_ASSERT(src_region.x + src_region.w <= src_dim.w); + SRB2_ASSERT(src_region.y + src_region.h <= src_dim.h); + + GLenum read_buffer = is_back ? GL_BACK_LEFT : GL_COLOR_ATTACHMENT0; + gl_->ReadBuffer(read_buffer); + GL_ASSERT; + + gl_->BindTexture(GL_TEXTURE_2D, tex.texture); + GL_ASSERT; + gl_->CopyTexSubImage2D(GL_TEXTURE_2D, 0, dst_region.x, dst_region.y, src_region.x, src_region.y, dst_region.w, dst_region.h); + GL_ASSERT; +} diff --git a/src/rhi/gl3_core/gl3_core_rhi.hpp b/src/rhi/gl3_core/gl3_core_rhi.hpp index 1284ea77d..51e838088 100644 --- a/src/rhi/gl3_core/gl3_core_rhi.hpp +++ b/src/rhi/gl3_core/gl3_core_rhi.hpp @@ -155,7 +155,7 @@ class GlCoreRhi final : public Rhi std::optional> current_pipeline_; PrimitiveType current_primitive_type_ = PrimitiveType::kPoints; bool graphics_context_active_ = false; - uint32_t graphics_context_generation_ = 0; + uint32_t graphics_context_generation_ = 1; uint32_t index_buffer_offset_ = 0; uint8_t stencil_front_reference_ = 0; @@ -223,6 +223,12 @@ public: virtual void draw_indexed(Handle ctx, uint32_t index_count, uint32_t first_index) override; virtual void read_pixels(Handle ctx, const Rect& rect, PixelFormat format, tcb::span out) override; + virtual void copy_framebuffer_to_texture( + Handle ctx, + Handle dst_tex, + const Rect& dst_region, + const Rect& src_region + ) override; virtual void set_stencil_reference(Handle ctx, CullMode face, uint8_t reference) override; virtual void set_stencil_compare_mask(Handle ctx, CullMode face, uint8_t mask) override; virtual void set_stencil_write_mask(Handle ctx, CullMode face, uint8_t mask) override; diff --git a/src/rhi/rhi.hpp b/src/rhi/rhi.hpp index 3587e2b26..c094cf990 100644 --- a/src/rhi/rhi.hpp +++ b/src/rhi/rhi.hpp @@ -582,6 +582,7 @@ struct TextureDetails /// @brief The unpack alignment of a row span when uploading pixels to the device. constexpr const std::size_t kPixelRowUnpackAlignment = 4; +constexpr const std::size_t kPixelRowPackAlignment = 4; /// @brief An active handle to a rendering device. struct Rhi @@ -638,6 +639,12 @@ struct Rhi virtual void draw_indexed(Handle ctx, uint32_t index_count, uint32_t first_index) = 0; virtual void read_pixels(Handle ctx, const Rect& rect, PixelFormat format, tcb::span out) = 0; + virtual void copy_framebuffer_to_texture( + Handle ctx, + Handle dst_tex, + const Rect& dst_region, + const Rect& src_region + ) = 0; virtual void set_stencil_reference(Handle ctx, CullMode face, uint8_t reference) = 0; virtual void set_stencil_compare_mask(Handle ctx, CullMode face, uint8_t mask) = 0; virtual void set_stencil_write_mask(Handle ctx, CullMode face, uint8_t mask) = 0; diff --git a/src/sdl/i_video.cpp b/src/sdl/i_video.cpp index cd5ba1848..0839a312f 100644 --- a/src/sdl/i_video.cpp +++ b/src/sdl/i_video.cpp @@ -190,7 +190,7 @@ static void SDLSetMode(INT32 width, INT32 height, SDL_bool fullscreen, SDL_bool if (fullscreen) { wasfullscreen = SDL_TRUE; - SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP); + SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN); } else // windowed mode { @@ -217,7 +217,7 @@ static void SDLSetMode(INT32 width, INT32 height, SDL_bool fullscreen, SDL_bool SDL_SetWindowSize(window, width, height); if (fullscreen) { - SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP); + SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN); } } diff --git a/src/v_video.cpp b/src/v_video.cpp index d472d4ab3..f69d505b5 100644 --- a/src/v_video.cpp +++ b/src/v_video.cpp @@ -3195,3 +3195,75 @@ void V_Recalc(void) vid.fsmalldupy = vid.smalldupy*FRACUNIT; #endif } + +void VID_DisplaySoftwareScreen() +{ + // TODO implement + // upload framebuffer, bind pipeline, draw + rhi::Rhi* rhi = srb2::sys::get_rhi(srb2::sys::g_current_rhi); + rhi::Handle ctx = srb2::sys::main_graphics_context(); + hwr2::HardwareState* hw_state = srb2::sys::main_hardware_state(); + + // Misnomer; this just uploads the screen to the software indexed screen texture + hw_state->software_screen_renderer->draw(*rhi, ctx); + + rhi->end_render_pass(ctx); + rhi->begin_default_render_pass(ctx, false); + + const int screens = std::clamp(r_splitscreen + 1, 1, MAXSPLITSCREENPLAYERS); + hw_state->blit_postimg_screens->set_num_screens(screens); + hw_state->blit_postimg_screens->set_target(static_cast(vid.width), static_cast(vid.height)); + + for (int i = 0; i < screens; i++) + { + glm::vec2 uv_offset {0.f, 0.f}; + glm::vec2 uv_size {1.f, 1.f}; + + if (screens > 2) + { + uv_size = glm::vec2(.5f, .5f); + switch (i) + { + case 0: + uv_offset = glm::vec2(0.f, 0.f); + break; + case 1: + uv_offset = glm::vec2(.5f, 0.f); + break; + case 2: + uv_offset = glm::vec2(0.f, .5f); + break; + case 3: + uv_offset = glm::vec2(.5f, .5f); + break; + } + } + else if (screens > 1) + { + uv_size = glm::vec2(1.f, .5f); + if (i == 1) + { + uv_offset = glm::vec2(0.f, .5f); + } + } + + hw_state->blit_postimg_screens->set_screen( + i, + { + hw_state->software_screen_renderer->screen(), + true, + uv_offset, + uv_size, + { + postimgtype[i] == postimg_water, + postimgtype[i] == postimg_heat, + postimgtype[i] == postimg_flip, + postimgtype[i] == postimg_mirror + } + } + ); + } + + // Post-process blit to the 'default' framebuffer + hw_state->blit_postimg_screens->draw(*rhi, ctx); +} diff --git a/src/v_video.h b/src/v_video.h index 2f4989a24..5c7ef5358 100644 --- a/src/v_video.h +++ b/src/v_video.h @@ -431,6 +431,12 @@ void V_DrawPatchFill(patch_t *pat); void VID_BlitLinearScreen(const UINT8 *srcptr, UINT8 *destptr, INT32 width, INT32 height, size_t srcrowbytes, size_t destrowbytes); +/** + * Display the software framebuffer to the screen. Added in RHI conversion; software is not implicitly displayed by the + * system. + */ +void VID_DisplaySoftwareScreen(void); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/y_inter.c b/src/y_inter.c index 9ea49d4a0..d3a26e164 100644 --- a/src/y_inter.c +++ b/src/y_inter.c @@ -1364,14 +1364,9 @@ void Y_IntermissionDrawer(void) //player icon 1 (55,79) 2 (55,93) 5 (183,79) - // If we early return, skip drawing the 3D scene (software buffer) so it doesn't clobber the frame for the wipe - g_wipeskiprender = true; - if (intertype == int_none || rendermode == render_none) return; - g_wipeskiprender = false; - fixed_t x; // Checker scroll @@ -1390,16 +1385,16 @@ void Y_IntermissionDrawer(void) // Draw the background K_DrawMapThumbnail(0, 0, BASEVIDWIDTH<