hwr2: Remove pass infrastructure

It's not worth trying to force the engine to conform to deferred
drawing.
This commit is contained in:
Eidolon 2023-08-27 20:54:31 -05:00
parent 4eb06392cb
commit 6f580606cd
39 changed files with 1125 additions and 1196 deletions

View file

@ -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

View file

@ -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.

View file

@ -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,

View file

@ -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();
}
}

View file

@ -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;

View file

@ -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<UINT8*>(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<UINT8*>(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<rhi::GraphicsContext> ctx, rhi::Handle<rhi::Texture>& tex)
{
bool recreate = false;
if (!tex)
{
recreate = true;
}
else
{
rhi::TextureDetails deets = rhi.get_texture_details(tex);
if (deets.width != static_cast<uint32_t>(vid.width) || deets.height != static_cast<uint32_t>(vid.height))
{
recreate = true;
rhi.destroy_texture(tex);
tex = rhi::kNullHandle;
}
}
if (!recreate)
{
return;
}
tex = rhi.create_texture({
rhi::TextureFormat::kRGBA,
static_cast<uint32_t>(vid.width),
static_cast<uint32_t>(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<rhi::GraphicsContext> 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<uint32_t>(vid.width), static_cast<uint32_t>(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<rhi::GraphicsContext> 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<uint32_t>(vid.width), static_cast<uint32_t>(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<lighttable_t*>(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<rhi::GraphicsContext> 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<uint32_t>(vid.width), static_cast<uint32_t>(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)
{

View file

@ -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;

View file

@ -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
)

View file

@ -7,7 +7,7 @@
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#include "pass_blit_postimg_screens.hpp"
#include "blit_postimg_screens.hpp"
#include <glm/mat3x3.hpp>
#include <glm/mat4x4.hpp>
@ -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<MainPaletteManager>& 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<int32_t>(h) / 2) : 0, w, (h / 2)};
default:
switch (screen)
{
case 2:
return {0, 0, w / 2, h / 2};
case 3:
return {static_cast<int32_t>(w) / 2, 0, w / 2, h / 2};
case 0:
return {0, static_cast<int32_t>(h) / 2, w / 2, h / 2};
case 1:
return {static_cast<int32_t>(w) / 2, static_cast<int32_t>(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<GraphicsContext> 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<int32_t>(h) / 2) : 0, w, (h / 2)};
default:
switch (screen)
{
case 2:
return {0, 0, w / 2, h / 2};
case 3:
return {static_cast<int32_t>(w) / 2, 0, w / 2, h / 2};
case 0:
return {0, static_cast<int32_t>(h) / 2, w / 2, h / 2};
case 1:
return {static_cast<int32_t>(w) / 2, static_cast<int32_t>(h) / 2, w / 2, h / 2};
}
}
return {0, 0, w, h};
}
void BlitPostimgScreens::transfer(Rhi& rhi, Handle<GraphicsContext> ctx)
{
// Upload needed buffers
@ -206,7 +223,7 @@ void BlitPostimgScreens::transfer(Rhi& rhi, Handle<GraphicsContext> ctx)
UniformVariant uniforms[] =
{
FixedToFloat(g_time.timefrac) + leveltime,
static_cast<float>(leveltime),
projection,
modelview,
texcoord_transform,
@ -221,33 +238,3 @@ void BlitPostimgScreens::transfer(Rhi& rhi, Handle<GraphicsContext> ctx)
screen_data_[i] = std::move(data);
}
}
void BlitPostimgScreens::graphics(Rhi& rhi, Handle<GraphicsContext> 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)
{
}

View file

@ -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<ScreenConfig, 4> screen_configs_;
srb2::StaticVec<ScreenData, 4> screen_data_;
rhi::Handle<rhi::Texture> target_;
uint32_t target_width_;
uint32_t target_height_;
std::shared_ptr<MainPaletteManager> palette_mgr_;
PaletteManager* palette_mgr_;
void prepass(rhi::Rhi& rhi);
void transfer(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx);
public:
BlitPostimgScreens(const std::shared_ptr<MainPaletteManager>& palette_mgr);
virtual ~BlitPostimgScreens();
explicit BlitPostimgScreens(PaletteManager* palette_mgr);
virtual void prepass(rhi::Rhi& rhi) override;
virtual void transfer(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx) override;
virtual void graphics(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx) override;
virtual void postpass(rhi::Rhi& rhi) override;
void draw(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx);
void set_num_screens(uint32_t screens) noexcept
{
@ -88,9 +85,8 @@ public:
screen_configs_[screen_index] = config;
}
void set_target(rhi::Handle<rhi::Texture> 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;
}

View file

@ -7,7 +7,7 @@
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#include "pass_blit_rect.hpp"
#include "blit_rect.hpp"
#include <optional>
@ -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<MainPaletteManager>& 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<GraphicsContext> 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<GraphicsContext> ctx)
@ -173,45 +130,17 @@ void BlitRectPass::transfer(Rhi& rhi, Handle<GraphicsContext> ctx)
uniform_sets_[1] = rhi.create_uniform_set(ctx, {g2_uniforms});
std::array<rhi::VertexAttributeBufferBinding, 1> vbs = {{{0, quad_vbo_}}};
if (palette_mgr_)
{
std::array<rhi::TextureBinding, 2> tbs = {
{{rhi::SamplerName::kSampler0, texture_}, {rhi::SamplerName::kSampler1, palette_mgr_->palette()}}};
binding_set_ = rhi.create_binding_set(ctx, pipeline_, {vbs, tbs});
}
else
{
std::array<rhi::TextureBinding, 1> tbs = {{{rhi::SamplerName::kSampler0, texture_}}};
binding_set_ = rhi.create_binding_set(ctx, pipeline_, {vbs, tbs});
}
std::array<rhi::TextureBinding, 1> 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<GraphicsContext> 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)
{
}

View file

@ -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 <array>
#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<rhi::Pipeline> pipeline_;
rhi::Handle<rhi::Texture> 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<rhi::RenderPass> render_pass_;
rhi::Handle<rhi::Buffer> quad_vbo_;
rhi::Handle<rhi::Buffer> quad_ibo_;
std::array<rhi::Handle<rhi::UniformSet>, 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<MainPaletteManager> palette_mgr_;
void prepass(rhi::Rhi& rhi);
void transfer(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx);
void graphics(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx);
public:
BlitRectPass();
BlitRectPass(bool output_clear);
BlitRectPass(const std::shared_ptr<MainPaletteManager>& palette_mgr, bool output_clear);
virtual ~BlitRectPass();
~BlitRectPass();
virtual void prepass(rhi::Rhi& rhi) override;
virtual void transfer(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx) override;
virtual void graphics(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx) override;
virtual void postpass(rhi::Rhi& rhi) override;
void draw(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> 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<rhi::Texture> 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__

View file

@ -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<rhi::Texture> start;
rhi::Handle<rhi::Texture> end;
};
struct HardwareState
{
std::unique_ptr<PaletteManager> palette_manager;
std::unique_ptr<FlatTextureManager> flat_manager;
std::unique_ptr<PatchAtlasCache> patch_atlas_cache;
std::unique_ptr<TwodeeRenderer> twodee_renderer;
std::unique_ptr<SoftwareScreenRenderer> software_screen_renderer;
std::unique_ptr<BlitPostimgScreens> blit_postimg_screens;
std::unique_ptr<PostprocessWipePass> wipe;
std::unique_ptr<BlitRectPass> blit_rect;
std::unique_ptr<ScreenshotPass> screen_capture;
WipeFrames wipe_frames;
};
} // srb2::hwr2
#endif // __SRB2_HWR2_HARDWARE_STATE_HPP__

View file

@ -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<GraphicsContext> ctx)
{
std::vector<std::array<uint8_t, 2>> flat_data;
for (auto flat_lump : to_upload_)
{
flat_data.clear();
Handle<Texture> 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<const uint8_t*>(W_CacheLumpNum(flat_lump, PU_PATCH));
SRB2_ASSERT(flat_memory != nullptr);
tcb::span<const uint8_t> 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<uint8_t>(0) : static_cast<uint8_t>(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<const std::byte> 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<GraphicsContext> ctx)
{
}
void FlatTextureManager::postpass(Rhi& rhi)
{
}
Handle<Texture> 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<Texture> 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<Texture> 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;
}

View file

@ -129,43 +129,6 @@ public:
rhi::Handle<rhi::Texture> 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<lumpnum_t, rhi::Handle<rhi::Texture>> flats_;
std::vector<lumpnum_t> to_upload_;
std::vector<rhi::Handle<rhi::Texture>> disposed_textures_;
public:
FlatTextureManager();
virtual ~FlatTextureManager();
virtual void prepass(rhi::Rhi& rhi) override;
virtual void transfer(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx) override;
virtual void graphics(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> 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<rhi::Texture> find_or_create_indexed(rhi::Rhi& rhi, lumpnum_t flat_lump);
rhi::Handle<rhi::Texture> find_indexed(lumpnum_t flat_lump) const;
};
} // namespace srb2::hwr2
#endif // __SRB2_HWR2_PASS_RESOURCE_MANAGERS_HPP__

View file

@ -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 <tcb/span.hpp>
#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<GraphicsContext> ctx)
{
}
void ScreenshotPass::graphics(Rhi& rhi, Handle<GraphicsContext> 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;
}

View file

@ -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<GraphicsContext> ctx)
{
// Prepare stbrp rects for patches to be loaded.
std::vector<stbrp_rect> 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<uint8_t> patch_data;
for (const patch_t* patch_to_upload : patches_to_upload_)
{
srb2::NotNull<PatchAtlas*> atlas = find_patch(patch_to_upload);
std::optional<PatchAtlas::Entry> 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<int32_t>(entry->x), static_cast<int32_t>(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<const patch_t*> patch)
@ -339,47 +364,3 @@ void PatchAtlasCache::queue_patch(srb2::NotNull<const patch_t*> patch)
patches_to_pack_.insert(patch);
}
void PatchAtlasCache::prepass(Rhi& rhi)
{
if (need_to_reset())
{
reset(rhi);
}
}
void PatchAtlasCache::transfer(Rhi& rhi, Handle<GraphicsContext> ctx)
{
SRB2_ASSERT(ready_for_lookup());
// Upload atlased patches
std::vector<uint8_t> patch_data;
for (const patch_t* patch_to_upload : patches_to_upload_)
{
srb2::NotNull<PatchAtlas*> atlas = find_patch(patch_to_upload);
std::optional<PatchAtlas::Entry> 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<int32_t>(entry->x), static_cast<int32_t>(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<GraphicsContext> ctx)
{
}
void PatchAtlasCache::postpass(Rhi& rhi)
{
}

View file

@ -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<PatchAtlas> atlases_;
std::unordered_map<const patch_t*, size_t> 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<const patch_t*> 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<rhi::GraphicsContext> 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<const patch_t*> patch) const;
PatchAtlas* find_patch(srb2::NotNull<const patch_t*> patch);
virtual void prepass(rhi::Rhi& rhi) override;
virtual void transfer(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx) override;
virtual void graphics(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> 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.

View file

@ -7,7 +7,7 @@
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#include "pass_postprocess.hpp"
#include "postprocess_wipe.hpp"
#include <string>
@ -62,23 +62,16 @@ PostprocessWipePass::PostprocessWipePass()
PostprocessWipePass::~PostprocessWipePass() = default;
void PostprocessWipePass::draw(Rhi& rhi, Handle<GraphicsContext> 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<GraphicsContext> 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)

View file

@ -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 <vector>
#include "../rhi/rhi.hpp"
namespace srb2::hwr2
{
class PostprocessWipePass final : public Pass
class PostprocessWipePass final
{
// Internal RHI resources
rhi::Handle<rhi::RenderPass> render_pass_;
rhi::Handle<rhi::Pipeline> pipeline_;
rhi::Handle<rhi::Buffer> vbo_;
bool upload_vbo_ = false;
@ -35,36 +34,36 @@ class PostprocessWipePass final : public Pass
// Pass parameters
rhi::Handle<rhi::Texture> start_;
rhi::Handle<rhi::Texture> end_;
rhi::Handle<rhi::Texture> target_;
uint32_t target_w_ = 0;
uint32_t target_h_ = 0;
uint32_t width_;
uint32_t height_;
// Mask lump loading
std::vector<uint8_t> mask_data_;
uint32_t mask_w_ = 0;
uint32_t mask_h_ = 0;
void prepass(rhi::Rhi& rhi);
void transfer(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx);
void graphics(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> 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<rhi::GraphicsContext> ctx) override;
virtual void graphics(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx) override;
virtual void postpass(rhi::Rhi& rhi) override;
void draw(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx);
void set_start(rhi::Handle<rhi::Texture> start) noexcept { start_ = start; }
void set_end(rhi::Handle<rhi::Texture> end) noexcept { end_ = end; }
void set_target(rhi::Handle<rhi::Texture> 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__

View file

@ -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<GraphicsContext> 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<byteColor_t, kPaletteSize> 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<const std::byte> 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<const std::byte> 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<uint8_t, kPaletteSize> 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<Texture> PaletteManager::find_or_create_colormap(Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx, srb2::NotNull<const uint8_t*> colormap)
{
if (colormaps_.find(colormap) != colormaps_.end())
{
return colormaps_[colormap];
}
Handle<Texture> texture = rhi.create_texture({TextureFormat::kLuminance, kPaletteSize, 1, TextureWrapMode::kClamp, TextureWrapMode::kClamp});
tcb::span<const std::byte> 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<Texture> PaletteManager::find_or_create_extra_lighttable(Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx, srb2::NotNull<const uint8_t*> lighttable)
{
if (lighttables_.find(lighttable) != lighttables_.end())
{
return lighttables_[lighttable];
}
Handle<Texture> texture = rhi.create_texture({TextureFormat::kLuminance, kPaletteSize, kLighttableRows, TextureWrapMode::kClamp, TextureWrapMode::kClamp});
tcb::span<const std::byte> 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<Texture> FlatTextureManager::find_or_create_indexed(Rhi& rhi, Handle<GraphicsContext> 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<Texture> new_tex = rhi.create_texture({
TextureFormat::kLuminanceAlpha,
flat_size,
flat_size,
TextureWrapMode::kRepeat,
TextureWrapMode::kRepeat
});
flats_.insert({lump, new_tex});
std::vector<std::array<uint8_t, 2>> flat_data;
std::size_t lump_length = W_LumpLength(lump);
flat_data.reserve(flat_size * flat_size);
const uint8_t* flat_memory = static_cast<const uint8_t*>(W_CacheLumpNum(lump, PU_PATCH));
SRB2_ASSERT(flat_memory != nullptr);
tcb::span<const uint8_t> 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<uint8_t>(0) : static_cast<uint8_t>(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<const std::byte> 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;
}

View file

@ -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<rhi::Texture> palette_;
rhi::Handle<rhi::Texture> lighttable_;
rhi::Handle<rhi::Texture> encore_lighttable_;
rhi::Handle<rhi::Texture> default_colormap_;
std::unordered_map<const uint8_t*, rhi::Handle<rhi::Texture>> colormaps_;
std::unordered_map<const uint8_t*, rhi::Handle<rhi::Texture>> lighttables_;
public:
PaletteManager();
PaletteManager(const PaletteManager&) = delete;
PaletteManager(PaletteManager&&);
~PaletteManager();
PaletteManager& operator=(const PaletteManager&) = delete;
PaletteManager& operator=(PaletteManager&&);
rhi::Handle<rhi::Texture> palette() const noexcept { return palette_; }
rhi::Handle<rhi::Texture> lighttable() const noexcept { return lighttable_; }
rhi::Handle<rhi::Texture> encore_lighttable() const noexcept { return encore_lighttable_; }
rhi::Handle<rhi::Texture> default_colormap() const noexcept { return default_colormap_; }
void update(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx);
void destroy_per_frame_resources(rhi::Rhi& rhi);
rhi::Handle<rhi::Texture> find_or_create_colormap(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx, srb2::NotNull<const uint8_t*> colormap);
rhi::Handle<rhi::Texture> find_or_create_extra_lighttable(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx, srb2::NotNull<const uint8_t*> 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<lumpnum_t, rhi::Handle<rhi::Texture>> flats_;
std::vector<lumpnum_t> to_upload_;
std::vector<rhi::Handle<rhi::Texture>> 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<rhi::Texture> find_or_create_indexed(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx, lumpnum_t flat_lump);
};
} // namespace srb2::hwr2
#endif // __SRB2_HWR2_RESOURCE_MANAGEMENT_HPP__

View file

@ -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 <tcb/span.hpp>
#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<GraphicsContext> 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<std::byte> 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_)));
}
}

View file

@ -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 <cstddef>
#include <vector>
#include "pass.hpp"
#include "../rhi/rhi.hpp"
namespace srb2::hwr2
{
class ScreenshotPass : public Pass
class ScreenshotPass
{
bool doing_screenshot_ = false;
rhi::Handle<rhi::Texture> source_;
rhi::Handle<rhi::RenderPass> render_pass_;
std::vector<uint8_t> pixel_data_;
std::vector<uint8_t> 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<rhi::GraphicsContext> ctx) override;
virtual void graphics(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx) override;
virtual void postpass(rhi::Rhi& rhi) override;
void capture(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx);
void set_source(rhi::Handle<rhi::Texture> 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__

View file

@ -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<GraphicsContext> 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<GraphicsContext> ctx)
{
// Upload screen
tcb::span<const std::byte> screen_span;
if (width_ % kPixelRowUnpackAlignment > 0)
@ -92,11 +81,3 @@ void SoftwarePass::transfer(Rhi& rhi, Handle<GraphicsContext> ctx)
rhi.update_texture(ctx, screen_texture_, {0, 0, width_, height_}, PixelFormat::kR8, screen_span);
}
void SoftwarePass::graphics(Rhi& rhi, Handle<GraphicsContext> ctx)
{
}
void SoftwarePass::postpass(Rhi& rhi)
{
}

View file

@ -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 <cstddef>
#include <vector>
#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<rhi::Texture> screen_texture_;
uint32_t width_ = 0;
@ -30,17 +30,14 @@ class SoftwarePass final : public Pass
std::vector<uint8_t> copy_buffer_;
public:
SoftwarePass();
virtual ~SoftwarePass();
SoftwareScreenRenderer();
~SoftwareScreenRenderer();
virtual void prepass(rhi::Rhi& rhi) override;
virtual void transfer(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx) override;
virtual void graphics(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx) override;
virtual void postpass(rhi::Rhi& rhi) override;
void draw(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx);
rhi::Handle<rhi::Texture> screen_texture() const noexcept { return screen_texture_; }
rhi::Handle<rhi::Texture> screen() const { return screen_texture_; }
};
} // namespace srb2::hwr2
#endif // __SRB2_HWR2_PASS_SOFTWARE_HPP_
#endif // __SRB2_HWR2_SOFTWARE_SCREEN_RENDERER_HPP_

View file

@ -7,7 +7,7 @@
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
#include "pass_twodee.hpp"
#include "twodee_renderer.hpp"
#include <unordered_set>
@ -23,23 +23,15 @@ using namespace srb2;
using namespace srb2::hwr2;
using namespace srb2::rhi;
struct srb2::hwr2::TwodeePassData
{
Handle<Texture> default_tex;
std::unordered_map<TwodeePipelineKey, Handle<Pipeline>> pipelines;
bool upload_default_tex = false;
};
std::shared_ptr<TwodeePassData> srb2::hwr2::make_twodee_pass_data()
{
return std::make_shared<TwodeePassData>();
}
TwodeePass::TwodeePass() : Pass()
{
}
TwodeePass::~TwodeePass() = default;
TwodeeRenderer::TwodeeRenderer(
srb2::NotNull<PaletteManager*> palette_manager,
srb2::NotNull<FlatTextureManager*> flat_manager,
srb2::NotNull<PatchAtlasCache*> 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<GraphicsContext> 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<uint8_t, 4> 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<GraphicsContext> 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<const patch_t*> 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<Buffer> 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<GraphicsContext> ctx)
{
if (!ctx_ || !data_)
{
return;
}
if (data_->upload_default_tex)
{
std::array<uint8_t, 4> 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<Texture> 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<GraphicsContext> ctx)
},
[&](const MergedTwodeeCommandFlatTexture& tex)
{
Handle<Texture> th = flat_manager_->find_indexed(tex.lump);
Handle<Texture> 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<GraphicsContext> 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<GraphicsContext> ctx)
Handle<Texture> 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<GraphicsContext> ctx)
// Sampler 0 Is Indexed Alpha (yes, it always is)
static_cast<int32_t>(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<GraphicsContext> 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<UniformSet> us_1 = rhi.create_uniform_set(ctx, {tcb::span(g1_uniforms)});
Handle<UniformSet> 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<GraphicsContext> ctx)
// This shouldn't happen, but, just in case...
continue;
}
SRB2_ASSERT(data_->pipelines.find(cmd.pipeline_key) != data_->pipelines.end());
Handle<Pipeline> pl = data_->pipelines[cmd.pipeline_key];
SRB2_ASSERT(pipelines_.find(cmd.pipeline_key) != pipelines_.end());
Handle<Pipeline> 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<uint32_t>(vid.width), static_cast<uint32_t>(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<GraphicsContext> 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);
}
}

View file

@ -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<srb2::hwr2::TwodeePipelineKey>
{
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<MergedTwodeeCommand> cmds;
};
std::shared_ptr<TwodeePassData> make_twodee_pass_data();
struct TwodeePass final : public Pass
class TwodeeRenderer final
{
Twodee* ctx_ = nullptr;
bool initialized_ = false;
std::variant<rhi::Handle<rhi::Texture>, rhi::Handle<rhi::Renderbuffer>> out_color_;
std::shared_ptr<TwodeePassData> data_;
std::shared_ptr<MainPaletteManager> palette_manager_;
std::shared_ptr<FlatTextureManager> flat_manager_;
std::shared_ptr<PatchAtlasCache> patch_atlas_cache_;
rhi::Handle<rhi::UniformSet> us_1;
rhi::Handle<rhi::UniformSet> us_2;
PaletteManager* palette_manager_;
FlatTextureManager* flat_manager_;
PatchAtlasCache* patch_atlas_cache_;
std::vector<MergedTwodeeCommandList> cmd_lists_;
std::vector<std::tuple<rhi::Handle<rhi::Buffer>, std::size_t>> vbos_;
std::vector<std::tuple<rhi::Handle<rhi::Buffer>, std::size_t>> ibos_;
rhi::Handle<rhi::RenderPass> render_pass_;
rhi::Handle<rhi::Texture> output_;
uint32_t output_width_ = 0;
uint32_t output_height_ = 0;
rhi::Handle<rhi::Texture> default_tex_;
std::unordered_map<TwodeePipelineKey, rhi::Handle<rhi::Pipeline>> pipelines_;
void rewrite_patch_quad_vertices(Draw2dList& list, const Draw2dPatchQuad& cmd) const;
TwodeePass();
virtual ~TwodeePass();
void initialize(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx);
virtual void prepass(rhi::Rhi& rhi) override;
public:
TwodeeRenderer(
srb2::NotNull<PaletteManager*> palette_manager,
srb2::NotNull<FlatTextureManager*> flat_manager,
srb2::NotNull<PatchAtlasCache*> 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<rhi::GraphicsContext> ctx) override;
virtual void graphics(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> 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<rhi::GraphicsContext> ctx, Twodee& twodee);
};
} // namespace srb2::hwr2
template <>
struct std::hash<srb2::hwr2::TwodeePipelineKey>
{
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__

View file

@ -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<rhi::Rhi> g_current_rhi;
rhi::Rhi* get_rhi(rhi::Handle<rhi::Rhi> handle);
} // namespace
rhi::Handle<rhi::GraphicsContext> 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

View file

@ -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<PassManager> resource_passmanager;
std::shared_ptr<PassManager> normal_rendering;
std::shared_ptr<PassManager> wipe_capture_start_rendering;
std::shared_ptr<PassManager> wipe_capture_end_rendering;
std::shared_ptr<PassManager> wipe_rendering;
};
} // namespace
static std::unique_ptr<InternalPassData> g_passes;
static Rhi* g_last_known_rhi = nullptr;
static bool g_imgui_frame_active = false;
static Handle<GraphicsContext> g_main_graphics_context;
static HardwareState g_hw_state;
Handle<Rhi> 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<PaletteManager>();
g_hw_state.flat_manager = std::make_unique<FlatTextureManager>();
g_hw_state.patch_atlas_cache = std::make_unique<PatchAtlasCache>(2048, 3);
g_hw_state.twodee_renderer = std::make_unique<TwodeeRenderer>(
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<SoftwareScreenRenderer>();
g_hw_state.blit_postimg_screens = std::make_unique<BlitPostimgScreens>(g_hw_state.palette_manager.get());
g_hw_state.wipe = std::make_unique<PostprocessWipePass>();
g_hw_state.blit_rect = std::make_unique<BlitRectPass>();
g_hw_state.screen_capture = std::make_unique<ScreenshotPass>();
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<FramebufferManager>();
auto palette_manager = std::make_shared<MainPaletteManager>();
auto common_resources_manager = std::make_shared<CommonResourcesManager>();
auto flat_texture_manager = std::make_shared<FlatTextureManager>();
auto patch_atlas_cache = std::make_shared<PatchAtlasCache>(2048, 2);
auto resource_manager = std::make_shared<PassManager>();
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<PassManager>();
auto software_pass = std::make_shared<SoftwarePass>();
auto blit_postimg_screens = std::make_shared<BlitPostimgScreens>(palette_manager);
auto twodee = std::make_shared<TwodeePass>();
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<BlitRectPass>(false);
auto screenshot_pass = std::make_shared<ScreenshotPass>();
auto imgui_pass = std::make_shared<ImguiPass>();
auto final_composite_pass = std::make_shared<BlitRectPass>(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<PassManager>();
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<PassManager>();
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<PassManager>();
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<PassManager>();
auto wipe_start_blit = std::make_shared<BlitRectPass>();
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<PassManager>();
auto wipe_end_blit = std::make_shared<BlitRectPass>();
auto wipe_end_blit_start_to_main = std::make_shared<BlitRectPass>();
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<PassManager>();
auto pp_wipe_pass = std::make_shared<PostprocessWipePass>();
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<Texture> start = framebuffer_manager->main_color();
Handle<Texture> 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<rhi::GraphicsContext> 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<rhi::GraphicsContext> ctx = srb2::sys::main_graphics_context();
hwr2::HardwareState* hw_state = srb2::sys::main_hardware_state();
hw_state->screen_capture->set_source(static_cast<uint32_t>(vid.width), static_cast<uint32_t>(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<InternalPassData>(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<rhi::GraphicsContext> 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<rhi::GraphicsContext> 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();
}

View file

@ -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)

View file

@ -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.

View file

@ -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<rhi::GraphicsContext> 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<GraphicsContext> 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<GraphicsContext> ctx, uint32_t vertex_count, uint32_t first_vertex)
@ -1688,9 +1698,38 @@ void GlCoreRhi::read_pixels(Handle<GraphicsContext> 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<GraphicsContext> ctx, CullMode face, uint8_t reference)
@ -1839,3 +1878,53 @@ void GlCoreRhi::finish()
disposal_.clear();
GL_ASSERT;
}
void GlCoreRhi::copy_framebuffer_to_texture(
Handle<GraphicsContext> ctx,
Handle<Texture> 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;
}

View file

@ -155,7 +155,7 @@ class GlCoreRhi final : public Rhi
std::optional<Handle<Pipeline>> 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<GraphicsContext> ctx, uint32_t index_count, uint32_t first_index) override;
virtual void
read_pixels(Handle<GraphicsContext> ctx, const Rect& rect, PixelFormat format, tcb::span<std::byte> out) override;
virtual void copy_framebuffer_to_texture(
Handle<GraphicsContext> ctx,
Handle<Texture> dst_tex,
const Rect& dst_region,
const Rect& src_region
) override;
virtual void set_stencil_reference(Handle<GraphicsContext> ctx, CullMode face, uint8_t reference) override;
virtual void set_stencil_compare_mask(Handle<GraphicsContext> ctx, CullMode face, uint8_t mask) override;
virtual void set_stencil_write_mask(Handle<GraphicsContext> ctx, CullMode face, uint8_t mask) override;

View file

@ -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<GraphicsContext> ctx, uint32_t index_count, uint32_t first_index) = 0;
virtual void
read_pixels(Handle<GraphicsContext> ctx, const Rect& rect, PixelFormat format, tcb::span<std::byte> out) = 0;
virtual void copy_framebuffer_to_texture(
Handle<GraphicsContext> ctx,
Handle<Texture> dst_tex,
const Rect& dst_region,
const Rect& src_region
) = 0;
virtual void set_stencil_reference(Handle<GraphicsContext> ctx, CullMode face, uint8_t reference) = 0;
virtual void set_stencil_compare_mask(Handle<GraphicsContext> ctx, CullMode face, uint8_t mask) = 0;
virtual void set_stencil_write_mask(Handle<GraphicsContext> ctx, CullMode face, uint8_t mask) = 0;

View file

@ -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);
}
}

View file

@ -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<rhi::GraphicsContext> 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<uint32_t>(vid.width), static_cast<uint32_t>(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);
}

View file

@ -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

View file

@ -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<<FRACBITS, (data.encore ? V_FLIP : 0), prevmap, bgcolor);
// Draw a mask over the BG to get the correct colorization
V_DrawMappedPatch(0, 0, V_ADD|V_TRANSLUCENT, mask, NULL);
// Draw the marquee (scroll pending)
//V_DrawMappedPatch(0, 154, V_SUBTRACT, rrmq, NULL);
// Draw the checker pattern (scroll pending)
//V_DrawMappedPatch(0, 0, V_SUBTRACT, rbgchk, NULL);
for (x = -mqscroll; x < (BASEVIDWIDTH * FRACUNIT); x += mqloop)
{
V_DrawFixedPatch(x, 154<<FRACBITS, FRACUNIT, V_SUBTRACT, rrmq, NULL);
@ -1519,16 +1514,16 @@ finalcounter:
{
switch (demo.savemode)
{
case DSM_NOTSAVING:
case DSM_NOTSAVING:
{
INT32 buttonx = BASEVIDWIDTH;
INT32 buttony = 2;
K_drawButtonAnim(buttonx - 76, buttony, 0, kp_button_b[1], replayprompttic);
V_DrawRightAlignedThinString(buttonx - 55, buttony, highlightflags, "or");
K_drawButtonAnim(buttonx - 55, buttony, 0, kp_button_x[1], replayprompttic);
V_DrawRightAlignedThinString(buttonx - 2, buttony, highlightflags, "Save replay");
break;
break;
}
case DSM_SAVED:
V_DrawRightAlignedThinString(BASEVIDWIDTH - 2, 2, highlightflags, "Replay saved!");
@ -1588,8 +1583,8 @@ void Y_Ticker(void)
{
replayprompttic++;
G_CheckDemoTitleEntry();
}
}
if (demo.savemode == DSM_WILLSAVE || demo.savemode == DSM_WILLAUTOSAVE)
G_SaveDemo();
}