mirror of
https://github.com/KartKrewDev/RingRacers.git
synced 2025-12-24 17:02:35 +00:00
hwr2: Add hardware 2D rendering
This commit is contained in:
parent
b1c0f2481b
commit
d855d96a10
46 changed files with 4225 additions and 886 deletions
|
|
@ -225,6 +225,7 @@ target_compile_definitions(SRB2SDL2 PRIVATE -DHAVE_DISCORDRPC -DUSE_STUN)
|
|||
target_sources(SRB2SDL2 PRIVATE discord.c stun.c)
|
||||
|
||||
target_link_libraries(SRB2SDL2 PRIVATE tcbrindle::span)
|
||||
target_link_libraries(SRB2SDL2 PRIVATE stb_rect_pack)
|
||||
target_link_libraries(SRB2SDL2 PRIVATE stb_vorbis)
|
||||
target_link_libraries(SRB2SDL2 PRIVATE xmp-lite::xmp-lite)
|
||||
target_link_libraries(SRB2SDL2 PRIVATE glad::glad)
|
||||
|
|
|
|||
|
|
@ -162,6 +162,17 @@ struct Overload : Ts... {
|
|||
template <typename... Ts>
|
||||
Overload(Ts...) -> Overload<Ts...>;
|
||||
|
||||
inline void hash_combine(std::size_t& seed)
|
||||
{}
|
||||
|
||||
template <class T, typename... Rest>
|
||||
inline void hash_combine(std::size_t& seed, const T& v, Rest... rest)
|
||||
{
|
||||
std::hash<T> hasher;
|
||||
seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
|
||||
hash_combine(seed, std::forward<Rest>(rest)...);
|
||||
}
|
||||
|
||||
} // namespace srb2
|
||||
|
||||
#endif // __SRB2_CXXUTIL_HPP__
|
||||
|
|
|
|||
|
|
@ -140,6 +140,10 @@ extern UINT16 curtttics;
|
|||
//
|
||||
|
||||
extern boolean WipeInAction;
|
||||
extern UINT8 g_wipetype;
|
||||
extern UINT8 g_wipeframe;
|
||||
extern boolean g_wipereverse;
|
||||
extern boolean g_wipeskiprender;
|
||||
extern boolean WipeStageTitle;
|
||||
|
||||
extern INT32 lastwipetic;
|
||||
|
|
|
|||
163
src/f_wipe.c
163
src/f_wipe.c
|
|
@ -90,6 +90,10 @@ UINT8 wipedefs[NUMWIPEDEFS] = {
|
|||
//--------------------------------------------------------------------------
|
||||
|
||||
boolean WipeInAction = false;
|
||||
UINT8 g_wipetype = 0;
|
||||
UINT8 g_wipeframe = 0;
|
||||
boolean g_wipereverse = false;
|
||||
boolean g_wipeskiprender = false;
|
||||
boolean WipeStageTitle = false;
|
||||
INT32 lastwipetic = 0;
|
||||
|
||||
|
|
@ -189,152 +193,6 @@ static fademask_t *F_GetFadeMask(UINT8 masknum, UINT8 scrnnum) {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
/** Wipe ticker
|
||||
*
|
||||
* \param fademask pixels to change
|
||||
*/
|
||||
static void F_DoWipe(fademask_t *fademask, lighttable_t *fadecolormap, boolean reverse)
|
||||
{
|
||||
// Software mask wipe -- optimized; though it might not look like it!
|
||||
// Okay, to save you wondering *how* this is more optimized than the simpler
|
||||
// version that came before it...
|
||||
// ---
|
||||
// The previous code did two FixedMul calls for every single pixel on the
|
||||
// screen, of which there are hundreds of thousands -- if not millions -- of.
|
||||
// This worked fine for smaller screen sizes, but with excessively large
|
||||
// (1920x1200) screens that meant 4 million+ calls out to FixedMul, and that
|
||||
// would take /just/ long enough that fades would start to noticably lag.
|
||||
// ---
|
||||
// This code iterates over the fade mask's pixels instead of the screen's,
|
||||
// and deals with drawing over each rectangular area before it moves on to
|
||||
// the next pixel in the fade mask. As a result, it's more complex (and might
|
||||
// look a little messy; sorry!) but it simultaneously runs at twice the speed.
|
||||
// In addition, we precalculate all the X and Y positions that we need to draw
|
||||
// from and to, so it uses a little extra memory, but again, helps it run faster.
|
||||
// ---
|
||||
// Sal: I kinda destroyed some of this code by introducing Genesis-style fades.
|
||||
// A colormap can be provided in F_RunWipe, which the white/black values will be
|
||||
// remapped to the appropriate entry in the fade colormap.
|
||||
{
|
||||
// wipe screen, start, end
|
||||
UINT8 *w = wipe_scr;
|
||||
const UINT8 *s = wipe_scr_start;
|
||||
const UINT8 *e = wipe_scr_end;
|
||||
|
||||
// first pixel for each screen
|
||||
UINT8 *w_base = w;
|
||||
const UINT8 *s_base = s;
|
||||
const UINT8 *e_base = e;
|
||||
|
||||
// mask data, end
|
||||
UINT8 *transtbl;
|
||||
const UINT8 *mask = fademask->mask;
|
||||
const UINT8 *maskend = mask + fademask->size;
|
||||
|
||||
// rectangle draw hints
|
||||
UINT32 draw_linestart, draw_rowstart;
|
||||
UINT32 draw_lineend, draw_rowend;
|
||||
UINT32 draw_linestogo, draw_rowstogo;
|
||||
|
||||
// rectangle coordinates, etc.
|
||||
UINT16* scrxpos = (UINT16*)malloc((fademask->width + 1) * sizeof(UINT16));
|
||||
UINT16* scrypos = (UINT16*)malloc((fademask->height + 1) * sizeof(UINT16));
|
||||
UINT16 maskx, masky;
|
||||
UINT32 relativepos;
|
||||
|
||||
// ---
|
||||
// Screw it, we do the fixed point math ourselves up front.
|
||||
scrxpos[0] = 0;
|
||||
for (relativepos = 0, maskx = 1; maskx < fademask->width; ++maskx)
|
||||
scrxpos[maskx] = (relativepos += fademask->xscale)>>FRACBITS;
|
||||
scrxpos[fademask->width] = vid.width;
|
||||
|
||||
scrypos[0] = 0;
|
||||
for (relativepos = 0, masky = 1; masky < fademask->height; ++masky)
|
||||
scrypos[masky] = (relativepos += fademask->yscale)>>FRACBITS;
|
||||
scrypos[fademask->height] = vid.height;
|
||||
// ---
|
||||
|
||||
maskx = masky = 0;
|
||||
do
|
||||
{
|
||||
UINT8 m = *mask;
|
||||
|
||||
draw_rowstart = scrxpos[maskx];
|
||||
draw_rowend = scrxpos[maskx + 1];
|
||||
draw_linestart = scrypos[masky];
|
||||
draw_lineend = scrypos[masky + 1];
|
||||
|
||||
relativepos = (draw_linestart * vid.width) + draw_rowstart;
|
||||
draw_linestogo = draw_lineend - draw_linestart;
|
||||
|
||||
if (reverse)
|
||||
m = ((pallen-1) - m);
|
||||
|
||||
if (m == 0)
|
||||
{
|
||||
// shortcut - memcpy source to work
|
||||
while (draw_linestogo--)
|
||||
{
|
||||
M_Memcpy(w_base+relativepos, (reverse ? e_base : s_base)+relativepos, draw_rowend-draw_rowstart);
|
||||
relativepos += vid.width;
|
||||
}
|
||||
}
|
||||
else if (m >= (pallen-1))
|
||||
{
|
||||
// shortcut - memcpy target to work
|
||||
while (draw_linestogo--)
|
||||
{
|
||||
M_Memcpy(w_base+relativepos, (reverse ? s_base : e_base)+relativepos, draw_rowend-draw_rowstart);
|
||||
relativepos += vid.width;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// pointer to transtable that this mask would use
|
||||
transtbl = transtables + ((9 - m)<<FF_TRANSSHIFT);
|
||||
|
||||
// DRAWING LOOP
|
||||
while (draw_linestogo--)
|
||||
{
|
||||
w = w_base + relativepos;
|
||||
s = s_base + relativepos;
|
||||
e = e_base + relativepos;
|
||||
draw_rowstogo = draw_rowend - draw_rowstart;
|
||||
|
||||
if (fadecolormap)
|
||||
{
|
||||
if (reverse)
|
||||
s = e;
|
||||
while (draw_rowstogo--)
|
||||
*w++ = fadecolormap[ ( m << 8 ) + *s++ ];
|
||||
}
|
||||
else while (draw_rowstogo--)
|
||||
{
|
||||
/*if (fadecolormap != NULL)
|
||||
{
|
||||
if (reverse)
|
||||
*w++ = fadecolormap[ ( m << 8 ) + *e++ ];
|
||||
else
|
||||
*w++ = fadecolormap[ ( m << 8 ) + *s++ ];
|
||||
}
|
||||
else*/
|
||||
*w++ = transtbl[ ( *e++ << 8 ) + *s++ ];
|
||||
}
|
||||
|
||||
relativepos += vid.width;
|
||||
}
|
||||
// END DRAWING LOOP
|
||||
}
|
||||
|
||||
if (++maskx >= fademask->width)
|
||||
++masky, maskx = 0;
|
||||
} while (++mask < maskend);
|
||||
|
||||
free(scrxpos);
|
||||
free(scrypos);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
/** Save the "before" screen of a wipe.
|
||||
|
|
@ -467,6 +325,7 @@ void F_RunWipe(UINT8 wipetype, boolean drawMenu, const char *colormap, boolean r
|
|||
|
||||
// Init the wipe
|
||||
WipeInAction = true;
|
||||
g_wipeskiprender = false;
|
||||
wipe_scr = screens[0];
|
||||
|
||||
// lastwipetic should either be 0 or the tic we last wiped
|
||||
|
|
@ -494,7 +353,10 @@ void F_RunWipe(UINT8 wipetype, boolean drawMenu, const char *colormap, boolean r
|
|||
|
||||
if (rendermode != render_none) //this allows F_RunWipe to be called in dedicated servers
|
||||
{
|
||||
F_DoWipe(fmask, fcolor, reverse);
|
||||
// F_DoWipe(fmask, fcolor, reverse);
|
||||
g_wipetype = wipetype;
|
||||
g_wipeframe = wipeframe - 1;
|
||||
g_wipereverse = reverse;
|
||||
|
||||
if (encorewiggle)
|
||||
{
|
||||
|
|
@ -521,6 +383,12 @@ void F_RunWipe(UINT8 wipetype, boolean drawMenu, const char *colormap, boolean r
|
|||
|
||||
I_FinishUpdate(); // 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;
|
||||
}
|
||||
|
||||
if (moviemode)
|
||||
M_SaveFrame();
|
||||
|
||||
|
|
@ -528,6 +396,7 @@ void F_RunWipe(UINT8 wipetype, boolean drawMenu, const char *colormap, boolean r
|
|||
}
|
||||
|
||||
WipeInAction = false;
|
||||
g_wipeskiprender = false;
|
||||
|
||||
if (fcolor)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,8 +1,20 @@
|
|||
target_sources(SRB2SDL2 PRIVATE
|
||||
pass_blit_rect.cpp
|
||||
pass_blit_rect.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_software.cpp
|
||||
pass_software.hpp
|
||||
pass_twodee.cpp
|
||||
pass_twodee.hpp
|
||||
pass.cpp
|
||||
pass.hpp
|
||||
twodee.cpp
|
||||
twodee.hpp
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,15 @@
|
|||
// 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.hpp"
|
||||
|
||||
srb2::hwr2::Pass::~Pass() = default;
|
||||
using namespace srb2;
|
||||
using namespace srb2::hwr2;
|
||||
|
||||
Pass::~Pass() = default;
|
||||
|
|
|
|||
|
|
@ -1,3 +1,12 @@
|
|||
// 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_PASS_HPP__
|
||||
#define __SRB2_HWR2_PASS_HPP__
|
||||
|
||||
|
|
@ -8,7 +17,9 @@ namespace srb2::hwr2
|
|||
|
||||
/// @brief A rendering pass which performs logic during each phase of a frame render.
|
||||
/// During rendering, all registered Pass's individual stages will be run together.
|
||||
struct Pass {
|
||||
class Pass
|
||||
{
|
||||
public:
|
||||
virtual ~Pass();
|
||||
|
||||
/// @brief Perform rendering logic and create necessary GPU resources.
|
||||
|
|
|
|||
209
src/hwr2/pass_blit_rect.cpp
Normal file
209
src/hwr2/pass_blit_rect.cpp
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
// 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_blit_rect.hpp"
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include <tcb/span.hpp>
|
||||
|
||||
#include "../cxxutil.hpp"
|
||||
|
||||
using namespace srb2;
|
||||
using namespace srb2::hwr2;
|
||||
using namespace srb2::rhi;
|
||||
|
||||
namespace
|
||||
{
|
||||
struct BlitVertex
|
||||
{
|
||||
float x = 0.f;
|
||||
float y = 0.f;
|
||||
float z = 0.f;
|
||||
float u = 0.f;
|
||||
float v = 0.f;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
static const BlitVertex kVerts[] =
|
||||
{{-.5f, -.5f, 0.f, 0.f, 0.f}, {.5f, -.5f, 0.f, 1.f, 0.f}, {-.5f, .5f, 0.f, 0.f, 1.f}, {.5f, .5f, 0.f, 1.f, 1.f}};
|
||||
|
||||
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,
|
||||
{PixelFormat::kRGBA8, 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,
|
||||
{{{sizeof(BlitVertex)}}, {{VertexAttributeName::kPosition, 0, 0}, {VertexAttributeName::kTexCoord0, 0, 12}}},
|
||||
{{{{UniformName::kProjection}}, {{UniformName::kModelView, UniformName::kTexCoord0Transform}}}},
|
||||
{{// RGB/A texture
|
||||
SamplerName::kSampler0}},
|
||||
std::nullopt,
|
||||
{PixelFormat::kRGBA8, std::nullopt, {true, true, true, true}},
|
||||
PrimitiveType::kTriangles,
|
||||
CullMode::kNone,
|
||||
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;
|
||||
|
||||
void BlitRectPass::prepass(Rhi& rhi)
|
||||
{
|
||||
if (!pipeline_)
|
||||
{
|
||||
if (palette_mgr_)
|
||||
{
|
||||
pipeline_ = rhi.create_pipeline(kPalettedPipelineDescription);
|
||||
}
|
||||
else
|
||||
{
|
||||
pipeline_ = rhi.create_pipeline(kUnshadedPipelineDescription);
|
||||
}
|
||||
}
|
||||
|
||||
if (!quad_vbo_)
|
||||
{
|
||||
quad_vbo_ = rhi.create_buffer({sizeof(kVerts), BufferType::kVertexBuffer, BufferUsage::kImmutable});
|
||||
quad_vbo_needs_upload_ = true;
|
||||
}
|
||||
|
||||
if (!quad_ibo_)
|
||||
{
|
||||
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(
|
||||
{std::nullopt,
|
||||
PixelFormat::kRGBA8,
|
||||
output_clear_ ? AttachmentLoadOp::kClear : AttachmentLoadOp::kLoad,
|
||||
AttachmentStoreOp::kStore}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void BlitRectPass::transfer(Rhi& rhi, Handle<TransferContext> ctx)
|
||||
{
|
||||
if (quad_vbo_needs_upload_ && quad_vbo_)
|
||||
{
|
||||
rhi.update_buffer_contents(ctx, quad_vbo_, 0, tcb::as_bytes(tcb::span(kVerts)));
|
||||
quad_vbo_needs_upload_ = false;
|
||||
}
|
||||
|
||||
if (quad_ibo_needs_upload_ && quad_ibo_)
|
||||
{
|
||||
rhi.update_buffer_contents(ctx, quad_ibo_, 0, tcb::as_bytes(tcb::span(kIndices)));
|
||||
quad_ibo_needs_upload_ = false;
|
||||
}
|
||||
|
||||
float aspect = 1.0;
|
||||
float output_aspect = 1.0;
|
||||
if (output_correct_aspect_)
|
||||
{
|
||||
aspect = static_cast<float>(texture_width_) / static_cast<float>(texture_height_);
|
||||
output_aspect = static_cast<float>(output_width_) / static_cast<float>(output_height_);
|
||||
}
|
||||
bool taller = aspect > output_aspect;
|
||||
|
||||
std::array<rhi::UniformVariant, 1> g1_uniforms = {{
|
||||
// Projection
|
||||
std::array<std::array<float, 4>, 4> {
|
||||
{{taller ? 1.f : 1.f / output_aspect, 0.f, 0.f, 0.f},
|
||||
{0.f, taller ? -1.f / (1.f / output_aspect) : -1.f, 0.f, 0.f},
|
||||
{0.f, 0.f, 1.f, 0.f},
|
||||
{0.f, 0.f, 0.f, 1.f}}},
|
||||
}};
|
||||
|
||||
std::array<rhi::UniformVariant, 2> g2_uniforms = {
|
||||
{// ModelView
|
||||
std::array<std::array<float, 4>, 4> {
|
||||
{{taller ? 2.f : 2.f * aspect, 0.f, 0.f, 0.f},
|
||||
{0.f, taller ? 2.f * (1.f / aspect) : 2.f, 0.f, 0.f},
|
||||
{0.f, 0.f, 1.f, 0.f},
|
||||
{0.f, 0.f, 0.f, 1.f}}},
|
||||
// Texcoord0 Transform
|
||||
std::array<std::array<float, 3>, 3> {
|
||||
{{1.f, 0.f, 0.f}, {0.f, output_flip_ ? -1.f : 1.f, 0.f}, {0.f, 0.f, 1.f}}}}};
|
||||
|
||||
uniform_sets_[0] = rhi.create_uniform_set(ctx, {g1_uniforms});
|
||||
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});
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr const rhi::Color 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.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)
|
||||
{
|
||||
}
|
||||
91
src/hwr2/pass_blit_rect.hpp
Normal file
91
src/hwr2/pass_blit_rect.hpp
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
// 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_PASS_BLIT_RECT_HPP__
|
||||
#define __SRB2_HWR2_PASS_BLIT_RECT_HPP__
|
||||
|
||||
#include <array>
|
||||
|
||||
#include "../rhi/rhi.hpp"
|
||||
#include "pass.hpp"
|
||||
#include "pass_resource_managers.hpp"
|
||||
|
||||
namespace srb2::hwr2
|
||||
{
|
||||
|
||||
/// @brief A render pass which blits a rect using a source texture or textures.
|
||||
class BlitRectPass final : public Pass
|
||||
{
|
||||
rhi::Handle<rhi::Pipeline> pipeline_;
|
||||
rhi::Handle<rhi::Texture> texture_;
|
||||
uint32_t texture_width_ = 0;
|
||||
uint32_t texture_height_ = 0;
|
||||
rhi::Handle<rhi::Texture> output_;
|
||||
uint32_t output_width_ = 0;
|
||||
uint32_t output_height_ = 0;
|
||||
bool output_correct_aspect_ = false;
|
||||
bool output_clear_ = true;
|
||||
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_;
|
||||
rhi::Handle<rhi::BindingSet> binding_set_;
|
||||
|
||||
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_;
|
||||
|
||||
public:
|
||||
BlitRectPass();
|
||||
BlitRectPass(bool output_clear);
|
||||
BlitRectPass(const std::shared_ptr<MainPaletteManager>& palette_mgr, bool output_clear);
|
||||
virtual ~BlitRectPass();
|
||||
|
||||
virtual void prepass(rhi::Rhi& rhi) override;
|
||||
virtual void transfer(rhi::Rhi& rhi, rhi::Handle<rhi::TransferContext> ctx) override;
|
||||
virtual void graphics(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx) override;
|
||||
virtual void postpass(rhi::Rhi& rhi) override;
|
||||
|
||||
/// @brief Set the next blit texture. Don't call during graphics phase!
|
||||
/// @param texture the texture to use when blitting
|
||||
/// @param width texture width
|
||||
/// @param height texture height
|
||||
void set_texture(rhi::Handle<rhi::Texture> texture, uint32_t width, uint32_t height) noexcept
|
||||
{
|
||||
texture_ = texture;
|
||||
texture_width_ = width;
|
||||
texture_height_ = height;
|
||||
}
|
||||
|
||||
/// @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;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace srb2::hwr2
|
||||
|
||||
#endif // __SRB2_HWR2_PASS_SOFTWARE_HPP__
|
||||
|
|
@ -1,3 +1,12 @@
|
|||
// 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_imgui.hpp"
|
||||
|
||||
#include <imgui.h>
|
||||
|
|
@ -8,48 +17,32 @@ using namespace srb2;
|
|||
using namespace srb2::hwr2;
|
||||
using namespace srb2::rhi;
|
||||
|
||||
static const PipelineDesc kPipelineDesc =
|
||||
{
|
||||
static const PipelineDesc kPipelineDesc = {
|
||||
PipelineProgram::kUnshaded,
|
||||
{
|
||||
{
|
||||
{sizeof(ImDrawVert)}
|
||||
},
|
||||
{
|
||||
{VertexAttributeName::kPosition, 0, 0},
|
||||
{VertexAttributeName::kTexCoord0, 0, 12},
|
||||
{VertexAttributeName::kColor, 0, 24}
|
||||
}
|
||||
},
|
||||
{{
|
||||
{{UniformName::kProjection}},
|
||||
{{UniformName::kModelView, UniformName::kTexCoord0Transform}}
|
||||
}},
|
||||
{{
|
||||
SamplerName::kSampler0
|
||||
}},
|
||||
PipelineDepthAttachmentDesc {
|
||||
PixelFormat::kDepth16,
|
||||
CompareFunc::kAlways,
|
||||
true
|
||||
},
|
||||
{
|
||||
PixelFormat::kRGBA8,
|
||||
BlendDesc {
|
||||
BlendFactor::kSourceAlpha,
|
||||
BlendFactor::kOneMinusSourceAlpha,
|
||||
BlendFunction::kAdd,
|
||||
BlendFactor::kOne,
|
||||
BlendFactor::kOneMinusSourceAlpha,
|
||||
BlendFunction::kAdd
|
||||
},
|
||||
{true, true, true, true}
|
||||
},
|
||||
{{{sizeof(ImDrawVert)}},
|
||||
{{VertexAttributeName::kPosition, 0, 0},
|
||||
{VertexAttributeName::kTexCoord0, 0, 12},
|
||||
{VertexAttributeName::kColor, 0, 24}}},
|
||||
{{{{UniformName::kProjection}}, {{UniformName::kModelView, UniformName::kTexCoord0Transform}}}},
|
||||
{{SamplerName::kSampler0}},
|
||||
PipelineDepthAttachmentDesc {PixelFormat::kDepth16, CompareFunc::kAlways, true},
|
||||
{PixelFormat::kRGBA8,
|
||||
BlendDesc {
|
||||
BlendFactor::kSourceAlpha,
|
||||
BlendFactor::kOneMinusSourceAlpha,
|
||||
BlendFunction::kAdd,
|
||||
BlendFactor::kOne,
|
||||
BlendFactor::kOneMinusSourceAlpha,
|
||||
BlendFunction::kAdd},
|
||||
{true, true, true, true}},
|
||||
PrimitiveType::kTriangles,
|
||||
CullMode::kNone,
|
||||
FaceWinding::kCounterClockwise,
|
||||
{0.f, 0.f, 0.f, 1.f}
|
||||
};
|
||||
{0.f, 0.f, 0.f, 1.f}};
|
||||
|
||||
ImguiPass::ImguiPass() : Pass()
|
||||
{
|
||||
}
|
||||
|
||||
ImguiPass::~ImguiPass() = default;
|
||||
|
||||
|
|
@ -86,18 +79,10 @@ void ImguiPass::prepass(Rhi& rhi)
|
|||
for (auto list : draw_lists)
|
||||
{
|
||||
Handle<Buffer> vbo = rhi.create_buffer(
|
||||
{
|
||||
static_cast<uint32_t>(list->VtxBuffer.size_in_bytes()),
|
||||
BufferType::kVertexBuffer,
|
||||
BufferUsage::kImmutable
|
||||
}
|
||||
{static_cast<uint32_t>(list->VtxBuffer.size_in_bytes()), BufferType::kVertexBuffer, BufferUsage::kImmutable}
|
||||
);
|
||||
Handle<Buffer> ibo = rhi.create_buffer(
|
||||
{
|
||||
static_cast<uint32_t>(list->IdxBuffer.size_in_bytes()),
|
||||
BufferType::kIndexBuffer,
|
||||
BufferUsage::kImmutable
|
||||
}
|
||||
{static_cast<uint32_t>(list->IdxBuffer.size_in_bytes()), BufferType::kIndexBuffer, BufferUsage::kImmutable}
|
||||
);
|
||||
|
||||
DrawList hwr2_list;
|
||||
|
|
@ -126,13 +111,11 @@ void ImguiPass::prepass(Rhi& rhi)
|
|||
draw_cmd.v_offset = cmd.VtxOffset;
|
||||
draw_cmd.i_offset = cmd.IdxOffset;
|
||||
draw_cmd.elems = cmd.ElemCount;
|
||||
draw_cmd.clip =
|
||||
{
|
||||
draw_cmd.clip = {
|
||||
static_cast<int32_t>(clip_min.x),
|
||||
static_cast<int32_t>((data->DisplaySize.y * data->FramebufferScale.y) - clip_max.y),
|
||||
static_cast<uint32_t>(clip_max.x - clip_min.x),
|
||||
static_cast<uint32_t>(clip_max.y - clip_min.y)
|
||||
};
|
||||
static_cast<uint32_t>(clip_max.y - clip_min.y)};
|
||||
hwr2_list.cmds.push_back(std::move(draw_cmd));
|
||||
}
|
||||
draw_lists_.push_back(std::move(hwr2_list));
|
||||
|
|
@ -179,35 +162,20 @@ void ImguiPass::transfer(Rhi& rhi, Handle<TransferContext> ctx)
|
|||
rhi.update_buffer_contents(ctx, ibo, 0, tcb::as_bytes(index_span));
|
||||
|
||||
// Uniform sets
|
||||
std::array<UniformVariant, 1> g1_uniforms =
|
||||
{{
|
||||
std::array<UniformVariant, 1> g1_uniforms = {{
|
||||
// Projection
|
||||
std::array<std::array<float, 4>, 4>
|
||||
{{
|
||||
{2.f / vid.realwidth, 0.f, 0.f, 0.f},
|
||||
{0.f, 2.f / vid.realheight, 0.f, 0.f},
|
||||
{0.f, 0.f, 1.f, 0.f},
|
||||
{-1.f, 1.f, 0.f, 1.f}
|
||||
}},
|
||||
}};
|
||||
std::array<UniformVariant, 2> g2_uniforms =
|
||||
{{
|
||||
// ModelView
|
||||
std::array<std::array<float, 4>, 4>
|
||||
{{
|
||||
{1.f, 0.f, 0.f, 0.f},
|
||||
{0.f, -1.f, 0.f, 0.f},
|
||||
{0.f, 0.f, 1.f, 0.f},
|
||||
{0.f, 0, 0.f, 1.f}
|
||||
}},
|
||||
// Texcoord0 Transform
|
||||
std::array<std::array<float, 3>, 3>
|
||||
{{
|
||||
{1.f, 0.f, 0.f},
|
||||
{0.f, 1.f, 0.f},
|
||||
{0.f, 0.f, 1.f}
|
||||
}}
|
||||
std::array<std::array<float, 4>, 4> {
|
||||
{{2.f / vid.realwidth, 0.f, 0.f, 0.f},
|
||||
{0.f, 2.f / vid.realheight, 0.f, 0.f},
|
||||
{0.f, 0.f, 1.f, 0.f},
|
||||
{-1.f, 1.f, 0.f, 1.f}}},
|
||||
}};
|
||||
std::array<UniformVariant, 2> g2_uniforms = {
|
||||
{// ModelView
|
||||
std::array<std::array<float, 4>, 4> {
|
||||
{{1.f, 0.f, 0.f, 0.f}, {0.f, -1.f, 0.f, 0.f}, {0.f, 0.f, 1.f, 0.f}, {0.f, 0, 0.f, 1.f}}},
|
||||
// Texcoord0 Transform
|
||||
std::array<std::array<float, 3>, 3> {{{1.f, 0.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 0.f, 1.f}}}}};
|
||||
Handle<UniformSet> us_1 = rhi.create_uniform_set(ctx, {g1_uniforms});
|
||||
Handle<UniformSet> us_2 = rhi.create_uniform_set(ctx, {g2_uniforms});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,12 @@
|
|||
// 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_PASS_IMGUI_HPP__
|
||||
#define __SRB2_HWR2_PASS_IMGUI_HPP__
|
||||
|
||||
|
|
@ -9,7 +18,7 @@
|
|||
namespace srb2::hwr2
|
||||
{
|
||||
|
||||
class ImguiPass : public Pass
|
||||
class ImguiPass final : public Pass
|
||||
{
|
||||
struct DrawCmd
|
||||
{
|
||||
|
|
@ -36,6 +45,7 @@ class ImguiPass : public Pass
|
|||
std::vector<DrawList> draw_lists_;
|
||||
|
||||
public:
|
||||
ImguiPass();
|
||||
virtual ~ImguiPass();
|
||||
|
||||
virtual void prepass(rhi::Rhi& rhi) override;
|
||||
|
|
|
|||
169
src/hwr2/pass_manager.cpp
Normal file
169
src/hwr2/pass_manager.cpp
Normal file
|
|
@ -0,0 +1,169 @@
|
|||
// 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_manager.hpp"
|
||||
|
||||
using namespace srb2;
|
||||
using namespace srb2::hwr2;
|
||||
using namespace srb2::rhi;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
class LambdaPass final : public Pass
|
||||
{
|
||||
PassManager* mgr_;
|
||||
std::function<void(PassManager&, rhi::Rhi&)> prepass_func_;
|
||||
std::function<void(PassManager&, rhi::Rhi&)> postpass_func_;
|
||||
|
||||
public:
|
||||
LambdaPass(PassManager* mgr, std::function<void(PassManager&, rhi::Rhi&)> prepass_func);
|
||||
LambdaPass(
|
||||
PassManager* mgr,
|
||||
std::function<void(PassManager&, rhi::Rhi&)> prepass_func,
|
||||
std::function<void(PassManager&, rhi::Rhi&)> postpass_func
|
||||
);
|
||||
virtual ~LambdaPass();
|
||||
|
||||
virtual void prepass(rhi::Rhi& rhi) override;
|
||||
virtual void transfer(rhi::Rhi& rhi, rhi::Handle<rhi::TransferContext> ctx) override;
|
||||
virtual void graphics(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx) override;
|
||||
virtual void postpass(rhi::Rhi& rhi) override;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
LambdaPass::LambdaPass(PassManager* mgr, std::function<void(PassManager&, rhi::Rhi&)> prepass_func)
|
||||
: mgr_(mgr), prepass_func_(prepass_func)
|
||||
{
|
||||
}
|
||||
|
||||
LambdaPass::LambdaPass(
|
||||
PassManager* mgr,
|
||||
std::function<void(PassManager&, rhi::Rhi&)> prepass_func,
|
||||
std::function<void(PassManager&, rhi::Rhi&)> postpass_func
|
||||
)
|
||||
: mgr_(mgr), prepass_func_(prepass_func), postpass_func_(postpass_func)
|
||||
{
|
||||
}
|
||||
|
||||
LambdaPass::~LambdaPass() = default;
|
||||
|
||||
void LambdaPass::prepass(Rhi& rhi)
|
||||
{
|
||||
if (prepass_func_)
|
||||
{
|
||||
(prepass_func_)(*mgr_, rhi);
|
||||
}
|
||||
}
|
||||
|
||||
void LambdaPass::transfer(Rhi&, Handle<TransferContext>)
|
||||
{
|
||||
}
|
||||
|
||||
void LambdaPass::graphics(Rhi&, Handle<GraphicsContext>)
|
||||
{
|
||||
}
|
||||
|
||||
void LambdaPass::postpass(Rhi& rhi)
|
||||
{
|
||||
if (postpass_func_)
|
||||
{
|
||||
(postpass_func_)(*mgr_, rhi);
|
||||
}
|
||||
}
|
||||
|
||||
PassManager::PassManager() = default;
|
||||
|
||||
void PassManager::insert(const std::string& name, std::shared_ptr<Pass> pass)
|
||||
{
|
||||
SRB2_ASSERT(pass_by_name_.find(name) == pass_by_name_.end());
|
||||
|
||||
std::size_t index = passes_.size();
|
||||
passes_.push_back(PassManagerEntry {name, pass, true});
|
||||
pass_by_name_.insert({name, index});
|
||||
}
|
||||
|
||||
void PassManager::insert(const std::string& name, std::function<void(PassManager&, Rhi&)> prepass_func)
|
||||
{
|
||||
insert(std::forward<const std::string>(name), std::make_shared<LambdaPass>(LambdaPass {this, prepass_func}));
|
||||
}
|
||||
|
||||
void PassManager::insert(
|
||||
const std::string& name,
|
||||
std::function<void(PassManager&, Rhi&)> prepass_func,
|
||||
std::function<void(PassManager&, Rhi&)> postpass_func
|
||||
)
|
||||
{
|
||||
insert(
|
||||
std::forward<const std::string>(name),
|
||||
std::make_shared<LambdaPass>(LambdaPass {this, prepass_func, postpass_func})
|
||||
);
|
||||
}
|
||||
|
||||
void PassManager::set_pass_enabled(const std::string& name, bool enabled)
|
||||
{
|
||||
SRB2_ASSERT(pass_by_name_.find(name) != pass_by_name_.end());
|
||||
|
||||
passes_[pass_by_name_[name]].enabled = enabled;
|
||||
}
|
||||
|
||||
std::weak_ptr<Pass> PassManager::for_name(const std::string& name)
|
||||
{
|
||||
auto itr = pass_by_name_.find(name);
|
||||
if (itr == pass_by_name_.end())
|
||||
{
|
||||
return std::weak_ptr<Pass>();
|
||||
}
|
||||
return passes_[itr->second].pass;
|
||||
}
|
||||
|
||||
void PassManager::render(Rhi& rhi) const
|
||||
{
|
||||
if (passes_.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto& pass : passes_)
|
||||
{
|
||||
if (pass.enabled)
|
||||
{
|
||||
pass.pass->prepass(rhi);
|
||||
}
|
||||
}
|
||||
|
||||
Handle<TransferContext> tc = rhi.begin_transfer();
|
||||
for (auto& pass : passes_)
|
||||
{
|
||||
if (pass.enabled)
|
||||
{
|
||||
pass.pass->transfer(rhi, tc);
|
||||
}
|
||||
}
|
||||
rhi.end_transfer(tc);
|
||||
|
||||
Handle<GraphicsContext> gc = rhi.begin_graphics();
|
||||
for (auto& pass : passes_)
|
||||
{
|
||||
if (pass.enabled)
|
||||
{
|
||||
pass.pass->graphics(rhi, gc);
|
||||
}
|
||||
}
|
||||
rhi.end_graphics(gc);
|
||||
|
||||
for (auto& pass : passes_)
|
||||
{
|
||||
if (pass.enabled)
|
||||
{
|
||||
pass.pass->postpass(rhi);
|
||||
}
|
||||
}
|
||||
}
|
||||
60
src/hwr2/pass_manager.hpp
Normal file
60
src/hwr2/pass_manager.hpp
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
// 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_PASS_MANAGER_HPP__
|
||||
#define __SRB2_HWR2_PASS_MANAGER_HPP__
|
||||
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "../rhi/rhi.hpp"
|
||||
#include "pass.hpp"
|
||||
|
||||
namespace srb2::hwr2
|
||||
{
|
||||
|
||||
class PassManager
|
||||
{
|
||||
struct PassManagerEntry
|
||||
{
|
||||
std::string name;
|
||||
std::shared_ptr<Pass> pass;
|
||||
bool enabled;
|
||||
};
|
||||
|
||||
std::unordered_map<std::string, std::size_t> pass_by_name_;
|
||||
std::vector<PassManagerEntry> passes_;
|
||||
|
||||
public:
|
||||
PassManager();
|
||||
PassManager(const PassManager&) = delete;
|
||||
PassManager(PassManager&&) = delete;
|
||||
PassManager& operator=(const PassManager&) = delete;
|
||||
PassManager& operator=(PassManager&&) = delete;
|
||||
|
||||
void insert(const std::string& name, std::shared_ptr<Pass> pass);
|
||||
void insert(const std::string& name, std::function<void(PassManager&, rhi::Rhi&)> prepass_func);
|
||||
void insert(
|
||||
const std::string& name,
|
||||
std::function<void(PassManager&, rhi::Rhi&)> prepass_func,
|
||||
std::function<void(PassManager&, rhi::Rhi&)> postpass_func
|
||||
);
|
||||
std::weak_ptr<Pass> for_name(const std::string& name);
|
||||
void set_pass_enabled(const std::string& name, bool enabled);
|
||||
|
||||
void render(rhi::Rhi& rhi) const;
|
||||
};
|
||||
|
||||
} // namespace srb2::hwr2
|
||||
|
||||
#endif // __SRB2_HWR2_PASS_MANAGER_HPP__
|
||||
217
src/hwr2/pass_postprocess.cpp
Normal file
217
src/hwr2/pass_postprocess.cpp
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
// 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_postprocess.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include <tcb/span.hpp>
|
||||
|
||||
#include "../f_finale.h"
|
||||
#include "../w_wad.h"
|
||||
|
||||
using namespace srb2;
|
||||
using namespace srb2::hwr2;
|
||||
using namespace srb2::rhi;
|
||||
|
||||
namespace
|
||||
{
|
||||
struct PostprocessVertex
|
||||
{
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
float u;
|
||||
float v;
|
||||
};
|
||||
|
||||
static const PostprocessVertex kPostprocessVerts[] =
|
||||
{{-.5f, -.5f, 0.f, 0.f, 0.f}, {.5f, -.5f, 0.f, 1.f, 0.f}, {-.5f, .5f, 0.f, 0.f, 1.f}, {.5f, .5f, 0.f, 1.f, 1.f}};
|
||||
|
||||
static const uint16_t kPostprocessIndices[] = {0, 1, 2, 1, 3, 2};
|
||||
|
||||
} // namespace
|
||||
|
||||
static const PipelineDesc kWipePipelineDesc = {
|
||||
PipelineProgram::kPostprocessWipe,
|
||||
{{{sizeof(PostprocessVertex)}},
|
||||
{
|
||||
{VertexAttributeName::kPosition, 0, 0},
|
||||
{VertexAttributeName::kTexCoord0, 0, 12},
|
||||
}},
|
||||
{{{{UniformName::kProjection}}}},
|
||||
{{SamplerName::kSampler0, SamplerName::kSampler1}},
|
||||
std::nullopt,
|
||||
{PixelFormat::kRGBA8, std::nullopt, {true, true, true, true}},
|
||||
PrimitiveType::kTriangles,
|
||||
CullMode::kNone,
|
||||
FaceWinding::kCounterClockwise,
|
||||
{0.f, 0.f, 0.f, 1.f}};
|
||||
|
||||
PostprocessWipePass::PostprocessWipePass() : Pass()
|
||||
{
|
||||
}
|
||||
|
||||
PostprocessWipePass::~PostprocessWipePass() = default;
|
||||
|
||||
void PostprocessWipePass::prepass(Rhi& rhi)
|
||||
{
|
||||
if (!render_pass_)
|
||||
{
|
||||
render_pass_ = rhi.create_render_pass(
|
||||
{std::nullopt, PixelFormat::kRGBA8, AttachmentLoadOp::kLoad, AttachmentStoreOp::kStore}
|
||||
);
|
||||
}
|
||||
|
||||
if (!pipeline_)
|
||||
{
|
||||
pipeline_ = rhi.create_pipeline(kWipePipelineDesc);
|
||||
}
|
||||
|
||||
if (!vbo_)
|
||||
{
|
||||
vbo_ = rhi.create_buffer({sizeof(PostprocessVertex) * 4, BufferType::kVertexBuffer, BufferUsage::kImmutable});
|
||||
upload_vbo_ = true;
|
||||
}
|
||||
if (!ibo_)
|
||||
{
|
||||
ibo_ = rhi.create_buffer({2 * 6, BufferType::kIndexBuffer, BufferUsage::kImmutable});
|
||||
upload_ibo_ = true;
|
||||
}
|
||||
|
||||
uint32_t wipe_type = g_wipetype;
|
||||
uint32_t wipe_frame = g_wipeframe;
|
||||
bool wipe_reverse = g_wipereverse;
|
||||
if (wipe_type >= 100 || wipe_frame >= 100)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::string lumpname = fmt::format(FMT_STRING("FADE{:02d}{:02d}"), wipe_type, wipe_frame);
|
||||
lumpnum_t mask_lump = W_CheckNumForName(lumpname.c_str());
|
||||
if (mask_lump == LUMPERROR)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::size_t mask_lump_size = W_LumpLength(mask_lump);
|
||||
switch (mask_lump_size)
|
||||
{
|
||||
case 256000:
|
||||
mask_w_ = 640;
|
||||
mask_h_ = 400;
|
||||
break;
|
||||
case 64000:
|
||||
mask_w_ = 320;
|
||||
mask_h_ = 200;
|
||||
break;
|
||||
case 16000:
|
||||
mask_w_ = 160;
|
||||
mask_h_ = 100;
|
||||
break;
|
||||
case 4000:
|
||||
mask_w_ = 80;
|
||||
mask_h_ = 50;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
mask_data_.clear();
|
||||
mask_data_.resize(mask_lump_size, 0);
|
||||
W_ReadLump(mask_lump, mask_data_.data());
|
||||
if (wipe_reverse)
|
||||
{
|
||||
for (auto& b : mask_data_)
|
||||
{
|
||||
b = 32 - b;
|
||||
}
|
||||
}
|
||||
|
||||
wipe_tex_ = rhi.create_texture({TextureFormat::kLuminance, mask_w_, mask_h_});
|
||||
}
|
||||
|
||||
void PostprocessWipePass::transfer(Rhi& rhi, Handle<TransferContext> ctx)
|
||||
{
|
||||
if (wipe_tex_ == kNullHandle)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (source_ == kNullHandle)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (upload_vbo_)
|
||||
{
|
||||
rhi.update_buffer_contents(ctx, vbo_, 0, tcb::as_bytes(tcb::span(kPostprocessVerts)));
|
||||
upload_vbo_ = false;
|
||||
}
|
||||
|
||||
if (upload_ibo_)
|
||||
{
|
||||
rhi.update_buffer_contents(ctx, ibo_, 0, tcb::as_bytes(tcb::span(kPostprocessIndices)));
|
||||
upload_ibo_ = false;
|
||||
}
|
||||
|
||||
tcb::span<const std::byte> data = tcb::as_bytes(tcb::span(mask_data_));
|
||||
rhi.update_texture(ctx, wipe_tex_, {0, 0, mask_w_, mask_h_}, PixelFormat::kR8, data);
|
||||
|
||||
UniformVariant uniforms[] = {
|
||||
{// Projection
|
||||
std::array<std::array<float, 4>, 4> {
|
||||
{{2.f, 0.f, 0.f, 0.f}, {0.f, 2.f, 0.f, 0.f}, {0.f, 0.f, 1.f, 0.f}, {0.f, 0.f, 0.f, 1.f}}}}};
|
||||
us_ = rhi.create_uniform_set(ctx, {tcb::span(uniforms)});
|
||||
|
||||
VertexAttributeBufferBinding vbos[] = {{0, vbo_}};
|
||||
TextureBinding tx[] = {{SamplerName::kSampler0, source_}, {SamplerName::kSampler1, wipe_tex_}};
|
||||
bs_ = rhi.create_binding_set(ctx, pipeline_, {vbos, tx});
|
||||
}
|
||||
|
||||
void PostprocessWipePass::graphics(Rhi& rhi, Handle<GraphicsContext> ctx)
|
||||
{
|
||||
if (wipe_tex_ == kNullHandle)
|
||||
{
|
||||
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.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)
|
||||
{
|
||||
if (wipe_tex_)
|
||||
{
|
||||
rhi.destroy_texture(wipe_tex_);
|
||||
wipe_tex_ = kNullHandle;
|
||||
}
|
||||
|
||||
mask_data_.clear();
|
||||
}
|
||||
71
src/hwr2/pass_postprocess.hpp
Normal file
71
src/hwr2/pass_postprocess.hpp
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
// 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_PASS_POSTPROCESS_HPP__
|
||||
#define __SRB2_HWR2_PASS_POSTPROCESS_HPP__
|
||||
|
||||
#include "pass.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace srb2::hwr2
|
||||
{
|
||||
|
||||
class PostprocessWipePass final : public Pass
|
||||
{
|
||||
rhi::Handle<rhi::RenderPass> render_pass_;
|
||||
rhi::Handle<rhi::Pipeline> pipeline_;
|
||||
rhi::Handle<rhi::Buffer> vbo_;
|
||||
bool upload_vbo_ = false;
|
||||
rhi::Handle<rhi::Buffer> ibo_;
|
||||
bool upload_ibo_ = false;
|
||||
rhi::Handle<rhi::UniformSet> us_;
|
||||
rhi::Handle<rhi::BindingSet> bs_;
|
||||
rhi::Handle<rhi::Texture> wipe_tex_;
|
||||
rhi::Handle<rhi::Texture> source_;
|
||||
uint32_t source_w_ = 0;
|
||||
uint32_t source_h_ = 0;
|
||||
rhi::Handle<rhi::Texture> end_;
|
||||
rhi::Handle<rhi::Texture> target_;
|
||||
uint32_t target_w_ = 0;
|
||||
uint32_t target_h_ = 0;
|
||||
|
||||
std::vector<uint8_t> mask_data_;
|
||||
uint32_t mask_w_ = 0;
|
||||
uint32_t mask_h_ = 0;
|
||||
|
||||
public:
|
||||
PostprocessWipePass();
|
||||
virtual ~PostprocessWipePass();
|
||||
|
||||
virtual void prepass(rhi::Rhi& rhi) override;
|
||||
virtual void transfer(rhi::Rhi& rhi, rhi::Handle<rhi::TransferContext> ctx) override;
|
||||
virtual void graphics(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx) override;
|
||||
virtual void postpass(rhi::Rhi& rhi) override;
|
||||
|
||||
void set_source(rhi::Handle<rhi::Texture> source, uint32_t width, uint32_t height) noexcept
|
||||
{
|
||||
source_ = source;
|
||||
source_w_ = width;
|
||||
source_h_ = height;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
target_ = target;
|
||||
target_w_ = width;
|
||||
target_h_ = height;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace srb2::hwr2
|
||||
|
||||
#endif // __SRB2_HWR2_PASS_POSTPROCESS_HPP__
|
||||
236
src/hwr2/pass_resource_managers.cpp
Normal file
236
src/hwr2/pass_resource_managers.cpp
Normal file
|
|
@ -0,0 +1,236 @@
|
|||
// 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_resource_managers.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#include "../v_video.h"
|
||||
#include "../z_zone.h"
|
||||
|
||||
using namespace srb2;
|
||||
using namespace srb2::hwr2;
|
||||
using namespace srb2::rhi;
|
||||
|
||||
FramebufferManager::FramebufferManager() : Pass()
|
||||
{
|
||||
}
|
||||
|
||||
FramebufferManager::~FramebufferManager() = default;
|
||||
|
||||
void FramebufferManager::prepass(Rhi& rhi)
|
||||
{
|
||||
uint32_t current_width = vid.width;
|
||||
uint32_t current_height = vid.height;
|
||||
|
||||
// Destroy the framebuffer textures if they exist and the video size changed
|
||||
if (width_ != current_width || height_ != current_height)
|
||||
{
|
||||
if (main_colors_[0] != kNullHandle)
|
||||
{
|
||||
rhi.destroy_texture(main_colors_[0]);
|
||||
main_colors_[0] = kNullHandle;
|
||||
}
|
||||
if (main_colors_[1] != kNullHandle)
|
||||
{
|
||||
rhi.destroy_texture(main_colors_[1]);
|
||||
main_colors_[1] = kNullHandle;
|
||||
}
|
||||
if (main_depth_ != kNullHandle)
|
||||
{
|
||||
rhi.destroy_renderbuffer(main_depth_);
|
||||
main_depth_ = kNullHandle;
|
||||
}
|
||||
|
||||
if (post_colors_[0] != kNullHandle)
|
||||
{
|
||||
rhi.destroy_texture(post_colors_[0]);
|
||||
post_colors_[0] = kNullHandle;
|
||||
}
|
||||
if (post_colors_[1] != kNullHandle)
|
||||
{
|
||||
rhi.destroy_texture(post_colors_[1]);
|
||||
post_colors_[1] = kNullHandle;
|
||||
}
|
||||
}
|
||||
width_ = current_width;
|
||||
height_ = current_height;
|
||||
|
||||
// Recreate the framebuffer textures
|
||||
if (main_colors_[0] == kNullHandle)
|
||||
{
|
||||
main_colors_[0] = rhi.create_texture({TextureFormat::kRGBA, current_width, current_height});
|
||||
}
|
||||
if (main_colors_[1] == kNullHandle)
|
||||
{
|
||||
main_colors_[1] = rhi.create_texture({TextureFormat::kRGBA, current_width, current_height});
|
||||
}
|
||||
if (main_depth_ == kNullHandle)
|
||||
{
|
||||
main_depth_ = rhi.create_renderbuffer({PixelFormat::kDepth16, current_width, current_height});
|
||||
}
|
||||
|
||||
if (post_colors_[0] == kNullHandle)
|
||||
{
|
||||
post_colors_[0] = rhi.create_texture({TextureFormat::kRGBA, current_width, current_height});
|
||||
}
|
||||
if (post_colors_[1] == kNullHandle)
|
||||
{
|
||||
post_colors_[1] = rhi.create_texture({TextureFormat::kRGBA, current_width, current_height});
|
||||
}
|
||||
}
|
||||
|
||||
void FramebufferManager::transfer(Rhi& rhi, Handle<TransferContext> ctx)
|
||||
{
|
||||
}
|
||||
|
||||
void FramebufferManager::graphics(Rhi& rhi, Handle<GraphicsContext> ctx)
|
||||
{
|
||||
}
|
||||
|
||||
void FramebufferManager::postpass(Rhi& rhi)
|
||||
{
|
||||
}
|
||||
|
||||
MainPaletteManager::MainPaletteManager() : Pass()
|
||||
{
|
||||
}
|
||||
|
||||
MainPaletteManager::~MainPaletteManager() = default;
|
||||
|
||||
void MainPaletteManager::prepass(Rhi& rhi)
|
||||
{
|
||||
if (!palette_)
|
||||
{
|
||||
palette_ = rhi.create_texture({TextureFormat::kRGBA, 256, 1});
|
||||
}
|
||||
}
|
||||
|
||||
void MainPaletteManager::transfer(Rhi& rhi, Handle<TransferContext> ctx)
|
||||
{
|
||||
std::array<byteColor_t, 256> palette_32;
|
||||
for (std::size_t i = 0; i < 256; i++)
|
||||
{
|
||||
palette_32[i] = V_GetColor(i).s;
|
||||
}
|
||||
rhi.update_texture(ctx, palette_, {0, 0, 256, 1}, PixelFormat::kRGBA8, tcb::as_bytes(tcb::span(palette_32)));
|
||||
}
|
||||
|
||||
void MainPaletteManager::graphics(Rhi& rhi, Handle<GraphicsContext> ctx)
|
||||
{
|
||||
}
|
||||
|
||||
void MainPaletteManager::postpass(Rhi& rhi)
|
||||
{
|
||||
}
|
||||
|
||||
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<TransferContext> 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});
|
||||
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;
|
||||
}
|
||||
129
src/hwr2/pass_resource_managers.hpp
Normal file
129
src/hwr2/pass_resource_managers.hpp
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
// 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_PASS_RESOURCE_MANAGERS_HPP__
|
||||
#define __SRB2_HWR2_PASS_RESOURCE_MANAGERS_HPP__
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "pass.hpp"
|
||||
|
||||
namespace srb2::hwr2
|
||||
{
|
||||
|
||||
class FramebufferManager final : public Pass
|
||||
{
|
||||
std::array<rhi::Handle<rhi::Texture>, 2> main_colors_;
|
||||
rhi::Handle<rhi::Renderbuffer> main_depth_;
|
||||
std::array<rhi::Handle<rhi::Texture>, 2> post_colors_;
|
||||
std::size_t main_index_ = 0;
|
||||
std::size_t post_index_ = 0;
|
||||
std::size_t width_ = 0;
|
||||
std::size_t height_ = 0;
|
||||
bool first_postprocess_ = true;
|
||||
|
||||
public:
|
||||
FramebufferManager();
|
||||
virtual ~FramebufferManager();
|
||||
|
||||
virtual void prepass(rhi::Rhi& rhi) override;
|
||||
virtual void transfer(rhi::Rhi& rhi, rhi::Handle<rhi::TransferContext> ctx) override;
|
||||
virtual void graphics(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx) override;
|
||||
virtual void postpass(rhi::Rhi& rhi) override;
|
||||
|
||||
/// @brief Swap the current and previous main colors.
|
||||
void swap_main() noexcept { main_index_ = main_index_ == 0 ? 1 : 0; }
|
||||
|
||||
/// @brief Swap the current and previous postprocess FB textures. Use between pass prepass phases to alternate.
|
||||
void swap_post() noexcept
|
||||
{
|
||||
post_index_ = post_index_ == 0 ? 1 : 0;
|
||||
first_postprocess_ = false;
|
||||
}
|
||||
|
||||
void reset_post() noexcept { first_postprocess_ = true; }
|
||||
|
||||
rhi::Handle<rhi::Texture> current_main_color() const noexcept { return main_colors_[main_index_]; }
|
||||
rhi::Handle<rhi::Renderbuffer> main_depth() const noexcept { return main_depth_; }
|
||||
rhi::Handle<rhi::Texture> previous_main_color() const noexcept { return main_colors_[1 - main_index_]; }
|
||||
|
||||
rhi::Handle<rhi::Texture> current_post_color() const noexcept { return post_colors_[post_index_]; }
|
||||
|
||||
rhi::Handle<rhi::Texture> previous_post_color() const noexcept
|
||||
{
|
||||
if (first_postprocess_)
|
||||
{
|
||||
return current_main_color();
|
||||
}
|
||||
return post_colors_[1 - post_index_];
|
||||
};
|
||||
|
||||
std::size_t width() const noexcept { return width_; }
|
||||
std::size_t height() const noexcept { return height_; }
|
||||
};
|
||||
|
||||
class MainPaletteManager final : public Pass
|
||||
{
|
||||
rhi::Handle<rhi::Texture> palette_;
|
||||
|
||||
public:
|
||||
MainPaletteManager();
|
||||
virtual ~MainPaletteManager();
|
||||
|
||||
virtual void prepass(rhi::Rhi& rhi) override;
|
||||
virtual void transfer(rhi::Rhi& rhi, rhi::Handle<rhi::TransferContext> ctx) override;
|
||||
virtual void graphics(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx) override;
|
||||
virtual void postpass(rhi::Rhi& rhi) override;
|
||||
|
||||
rhi::Handle<rhi::Texture> palette() const noexcept { return palette_; }
|
||||
};
|
||||
|
||||
/*
|
||||
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::TransferContext> 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__
|
||||
|
|
@ -1,10 +1,17 @@
|
|||
// 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_software.hpp"
|
||||
|
||||
#include <optional>
|
||||
#include "../i_video.h"
|
||||
#include "../v_video.h"
|
||||
|
||||
#include <tcb/span.hpp>
|
||||
|
||||
#include "../cxxutil.hpp"
|
||||
#include "../d_netcmd.h"
|
||||
#ifdef HAVE_DISCORDRPC
|
||||
#include "../discord.h"
|
||||
|
|
@ -13,82 +20,13 @@
|
|||
#include "../m_avrecorder.h"
|
||||
#include "../st_stuff.h"
|
||||
#include "../s_sound.h"
|
||||
#include "../st_stuff.h"
|
||||
#include "../v_video.h"
|
||||
|
||||
using namespace srb2;
|
||||
using namespace srb2::hwr2;
|
||||
using namespace srb2::rhi;
|
||||
|
||||
SoftwareBlitPass::~SoftwareBlitPass() = default;
|
||||
|
||||
namespace
|
||||
{
|
||||
struct SwBlitVertex
|
||||
{
|
||||
float x = 0.f;
|
||||
float y = 0.f;
|
||||
float z = 0.f;
|
||||
float u = 0.f;
|
||||
float v = 0.f;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
static const SwBlitVertex kVerts[] =
|
||||
{
|
||||
{-.5f, -.5f, 0.f, 0.f, 0.f},
|
||||
{.5f, -.5f, 0.f, 1.f, 0.f},
|
||||
{-.5f, .5f, 0.f, 0.f, 1.f},
|
||||
{.5f, .5f, 0.f, 1.f, 1.f}
|
||||
};
|
||||
|
||||
static const uint16_t kIndices[] = {0, 1, 2, 1, 3, 2};
|
||||
|
||||
static const PipelineDesc kPipelineDescription =
|
||||
{
|
||||
PipelineProgram::kUnshadedPaletted,
|
||||
{
|
||||
{
|
||||
{sizeof(SwBlitVertex)}
|
||||
},
|
||||
{
|
||||
{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,
|
||||
{
|
||||
PixelFormat::kRGBA8,
|
||||
std::nullopt,
|
||||
{true, true, true, true}
|
||||
},
|
||||
PrimitiveType::kTriangles,
|
||||
CullMode::kNone,
|
||||
FaceWinding::kCounterClockwise,
|
||||
{0.f, 0.f, 0.f, 1.f}
|
||||
};
|
||||
|
||||
static uint32_t next_pow_of_2(uint32_t in)
|
||||
{
|
||||
in--;
|
||||
in |= in >> 1;
|
||||
in |= in >> 2;
|
||||
in |= in >> 4;
|
||||
in |= in >> 8;
|
||||
in |= in >> 16;
|
||||
in++;
|
||||
return in;
|
||||
}
|
||||
|
||||
static void temp_legacy_finishupdate_draws()
|
||||
{
|
||||
SCR_CalculateFPS();
|
||||
|
|
@ -100,8 +38,7 @@ static void temp_legacy_finishupdate_draws()
|
|||
if (cv_ticrate.value)
|
||||
SCR_DisplayTicRate();
|
||||
|
||||
if (cv_showping.value && netgame &&
|
||||
( consoleplayer != serverplayer || ! server_lagless ))
|
||||
if (cv_showping.value && netgame && (consoleplayer != serverplayer || !server_lagless))
|
||||
{
|
||||
if (server_lagless)
|
||||
{
|
||||
|
|
@ -110,11 +47,8 @@ static void temp_legacy_finishupdate_draws()
|
|||
}
|
||||
else
|
||||
{
|
||||
for (
|
||||
int player = 1;
|
||||
player < MAXPLAYERS;
|
||||
player++
|
||||
){
|
||||
for (int player = 1; player < MAXPLAYERS; player++)
|
||||
{
|
||||
if (D_IsPlayerHumanAndGaming(player))
|
||||
{
|
||||
SCR_DisplayLocalPing();
|
||||
|
|
@ -142,149 +76,83 @@ static void temp_legacy_finishupdate_draws()
|
|||
#endif
|
||||
}
|
||||
|
||||
void SoftwareBlitPass::prepass(Rhi& rhi)
|
||||
SoftwarePass::SoftwarePass() : Pass()
|
||||
{
|
||||
if (!pipeline_)
|
||||
}
|
||||
|
||||
SoftwarePass::~SoftwarePass() = default;
|
||||
|
||||
void SoftwarePass::prepass(Rhi& rhi)
|
||||
{
|
||||
if (rendermode != render_soft)
|
||||
{
|
||||
pipeline_ = rhi.create_pipeline(kPipelineDescription);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!quad_vbo_)
|
||||
{
|
||||
quad_vbo_ = rhi.create_buffer({sizeof(kVerts), BufferType::kVertexBuffer, BufferUsage::kImmutable});
|
||||
quad_vbo_needs_upload_ = true;
|
||||
}
|
||||
|
||||
if (!quad_ibo_)
|
||||
{
|
||||
quad_ibo_ = rhi.create_buffer({sizeof(kIndices), BufferType::kIndexBuffer, BufferUsage::kImmutable});
|
||||
quad_ibo_needs_upload_ = true;
|
||||
}
|
||||
// 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.
|
||||
|
||||
temp_legacy_finishupdate_draws();
|
||||
|
||||
uint32_t vid_width = static_cast<uint32_t>(vid.width);
|
||||
uint32_t vid_height = static_cast<uint32_t>(vid.height);
|
||||
|
||||
if (screen_tex_ && (screen_tex_width_ < vid_width || screen_tex_height_ < vid_height))
|
||||
// Prepare RHI resources
|
||||
if (screen_texture_ && (static_cast<int32_t>(width_) != vid.width || static_cast<int32_t>(height_) != vid.height))
|
||||
{
|
||||
rhi.destroy_texture(screen_tex_);
|
||||
screen_tex_ = kNullHandle;
|
||||
// Mode changed, recreate texture
|
||||
rhi.destroy_texture(screen_texture_);
|
||||
screen_texture_ = kNullHandle;
|
||||
}
|
||||
|
||||
if (!screen_tex_)
|
||||
width_ = vid.width;
|
||||
height_ = vid.height;
|
||||
|
||||
if (!screen_texture_)
|
||||
{
|
||||
screen_tex_width_ = next_pow_of_2(vid_width);
|
||||
screen_tex_height_ = next_pow_of_2(vid_height);
|
||||
screen_tex_ = rhi.create_texture({TextureFormat::kLuminance, screen_tex_width_, screen_tex_height_});
|
||||
screen_texture_ = rhi.create_texture({TextureFormat::kLuminance, width_, height_});
|
||||
}
|
||||
|
||||
if (!palette_tex_)
|
||||
// If the screen width won't fit the unpack alignment, we need to copy the screen.
|
||||
if (width_ % kPixelRowUnpackAlignment > 0)
|
||||
{
|
||||
palette_tex_ = rhi.create_texture({TextureFormat::kRGBA, 256, 1});
|
||||
std::size_t padded_width = (width_ + (kPixelRowUnpackAlignment - 1)) & !kPixelRowUnpackAlignment;
|
||||
copy_buffer_.clear();
|
||||
copy_buffer_.reserve(padded_width * height_);
|
||||
for (std::size_t y = 0; y < height_; y++)
|
||||
{
|
||||
for (std::size_t x = 0; x < width_; x++)
|
||||
{
|
||||
copy_buffer_.push_back(vid.buffer[(width_ * y) + x]);
|
||||
}
|
||||
|
||||
// Padding to unpack alignment
|
||||
for (std::size_t i = 0; i < padded_width - width_; i++)
|
||||
{
|
||||
copy_buffer_.push_back(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SoftwareBlitPass::upload_screen(Rhi& rhi, Handle<TransferContext> ctx)
|
||||
void SoftwarePass::transfer(Rhi& rhi, Handle<TransferContext> ctx)
|
||||
{
|
||||
rhi::Rect screen_rect = {
|
||||
0,
|
||||
0,
|
||||
static_cast<uint32_t>(vid.width),
|
||||
static_cast<uint32_t>(vid.height)
|
||||
};
|
||||
|
||||
tcb::span<uint8_t> screen_span = tcb::span(vid.buffer, static_cast<size_t>(vid.width * vid.height));
|
||||
rhi.update_texture(ctx, screen_tex_, screen_rect, rhi::PixelFormat::kR8, tcb::as_bytes(screen_span));
|
||||
}
|
||||
|
||||
void SoftwareBlitPass::upload_palette(Rhi& rhi, Handle<TransferContext> ctx)
|
||||
{
|
||||
// Unfortunately, pMasterPalette must be swizzled to get a linear layout.
|
||||
// Maybe some adjustments to palette storage can make this a straight upload.
|
||||
std::array<byteColor_t, 256> palette_32;
|
||||
for (size_t i = 0; i < 256; i++)
|
||||
// Upload screen
|
||||
tcb::span<const std::byte> screen_span;
|
||||
if (width_ % kPixelRowUnpackAlignment > 0)
|
||||
{
|
||||
palette_32[i] = pMasterPalette[i].s;
|
||||
screen_span = tcb::as_bytes(tcb::span(copy_buffer_));
|
||||
}
|
||||
rhi.update_texture(ctx, palette_tex_, {0, 0, 256, 1}, rhi::PixelFormat::kRGBA8, tcb::as_bytes(tcb::span(palette_32)));
|
||||
}
|
||||
|
||||
void SoftwareBlitPass::transfer(Rhi& rhi, Handle<TransferContext> ctx)
|
||||
{
|
||||
if (quad_vbo_needs_upload_ && quad_vbo_)
|
||||
else
|
||||
{
|
||||
rhi.update_buffer_contents(ctx, quad_vbo_, 0, tcb::as_bytes(tcb::span(kVerts)));
|
||||
quad_vbo_needs_upload_ = false;
|
||||
screen_span = tcb::as_bytes(tcb::span(vid.buffer, width_ * height_));
|
||||
}
|
||||
|
||||
if (quad_ibo_needs_upload_ && quad_ibo_)
|
||||
{
|
||||
rhi.update_buffer_contents(ctx, quad_ibo_, 0, tcb::as_bytes(tcb::span(kIndices)));
|
||||
quad_ibo_needs_upload_ = false;
|
||||
}
|
||||
|
||||
upload_screen(rhi, ctx);
|
||||
upload_palette(rhi, ctx);
|
||||
|
||||
// Calculate aspect ratio for black borders
|
||||
float aspect = static_cast<float>(vid.width) / static_cast<float>(vid.height);
|
||||
float real_aspect = static_cast<float>(vid.realwidth) / static_cast<float>(vid.realheight);
|
||||
bool taller = aspect > real_aspect;
|
||||
|
||||
std::array<rhi::UniformVariant, 1> g1_uniforms = {{
|
||||
// Projection
|
||||
std::array<std::array<float, 4>, 4> {{
|
||||
{taller ? 1.f : 1.f / real_aspect, 0.f, 0.f, 0.f},
|
||||
{0.f, taller ? -1.f / (1.f / real_aspect) : -1.f, 0.f, 0.f},
|
||||
{0.f, 0.f, 1.f, 0.f},
|
||||
{0.f, 0.f, 0.f, 1.f}
|
||||
}},
|
||||
}};
|
||||
|
||||
std::array<rhi::UniformVariant, 2> g2_uniforms =
|
||||
{{
|
||||
// ModelView
|
||||
std::array<std::array<float, 4>, 4>
|
||||
{{
|
||||
{taller ? 2.f : 2.f * aspect, 0.f, 0.f, 0.f},
|
||||
{0.f, taller ? 2.f * (1.f / aspect) : 2.f, 0.f, 0.f},
|
||||
{0.f, 0.f, 1.f, 0.f},
|
||||
{0.f, 0.f, 0.f, 1.f}
|
||||
}},
|
||||
// Texcoord0 Transform
|
||||
std::array<std::array<float, 3>, 3>
|
||||
{{
|
||||
{vid.width / static_cast<float>(screen_tex_width_), 0.f, 0.f},
|
||||
{0.f, vid.height / static_cast<float>(screen_tex_height_), 0.f},
|
||||
{0.f, 0.f, 1.f}
|
||||
}}
|
||||
}};
|
||||
|
||||
uniform_sets_[0] = rhi.create_uniform_set(ctx, {g1_uniforms});
|
||||
uniform_sets_[1] = rhi.create_uniform_set(ctx, {g2_uniforms});
|
||||
|
||||
std::array<rhi::VertexAttributeBufferBinding, 1> vbs = {{{0, quad_vbo_}}};
|
||||
std::array<rhi::TextureBinding, 2> tbs = {{
|
||||
{rhi::SamplerName::kSampler0, screen_tex_},
|
||||
{rhi::SamplerName::kSampler1, palette_tex_}
|
||||
}};
|
||||
binding_set_ = rhi.create_binding_set(ctx, pipeline_, {vbs, tbs});
|
||||
rhi.update_texture(ctx, screen_texture_, {0, 0, width_, height_}, PixelFormat::kR8, screen_span);
|
||||
}
|
||||
|
||||
void SoftwareBlitPass::graphics(Rhi& rhi, Handle<GraphicsContext> ctx)
|
||||
void SoftwarePass::graphics(Rhi& rhi, Handle<GraphicsContext> ctx)
|
||||
{
|
||||
rhi.begin_default_render_pass(ctx, true);
|
||||
rhi.bind_pipeline(ctx, pipeline_);
|
||||
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 SoftwareBlitPass::postpass(Rhi& rhi)
|
||||
void SoftwarePass::postpass(Rhi& rhi)
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,44 +1,46 @@
|
|||
#ifndef __SRB2_HWR2_PASS_SOFTWARE_HPP__
|
||||
#define __SRB2_HWR2_PASS_SOFTWARE_HPP__
|
||||
// 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 <array>
|
||||
#ifndef __SRB2_HWR2_PASS_SOFTWARE_HPP_
|
||||
#define __SRB2_HWR2_PASS_SOFTWARE_HPP_
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
#include "../rhi/rhi.hpp"
|
||||
#include "pass.hpp"
|
||||
|
||||
namespace srb2::hwr2
|
||||
{
|
||||
|
||||
class SoftwareBlitPass : public Pass
|
||||
/// @brief Renders software player views in prepass and uploads the result to a texture in transfer.
|
||||
class SoftwarePass final : public Pass
|
||||
{
|
||||
rhi::Handle<rhi::Pipeline> pipeline_;
|
||||
rhi::Handle<rhi::Texture> screen_tex_;
|
||||
rhi::Handle<rhi::Texture> palette_tex_;
|
||||
rhi::Handle<rhi::Buffer> quad_vbo_;
|
||||
rhi::Handle<rhi::Buffer> quad_ibo_;
|
||||
std::array<rhi::Handle<rhi::UniformSet>, 2> uniform_sets_;
|
||||
rhi::Handle<rhi::BindingSet> binding_set_;
|
||||
rhi::Handle<rhi::Texture> screen_texture_;
|
||||
uint32_t width_ = 0;
|
||||
uint32_t height_ = 0;
|
||||
|
||||
uint32_t screen_tex_width_ = 0;
|
||||
uint32_t screen_tex_height_ = 0;
|
||||
bool quad_vbo_needs_upload_ = false;
|
||||
bool quad_ibo_needs_upload_ = false;
|
||||
|
||||
void upload_screen(rhi::Rhi& rhi, rhi::Handle<rhi::TransferContext> ctx);
|
||||
void upload_palette(rhi::Rhi& rhi, rhi::Handle<rhi::TransferContext> ctx);
|
||||
// Used to ensure the row spans are aligned on the unpack boundary for weird resolutions
|
||||
// Any resolution with a width divisible by 4 doesn't need this, but e.g. 1366x768 needs the intermediary copy
|
||||
std::vector<uint8_t> copy_buffer_;
|
||||
|
||||
public:
|
||||
virtual ~SoftwareBlitPass();
|
||||
SoftwarePass();
|
||||
virtual ~SoftwarePass();
|
||||
|
||||
virtual void prepass(rhi::Rhi& rhi) override;
|
||||
|
||||
virtual void transfer(rhi::Rhi& rhi, rhi::Handle<rhi::TransferContext> ctx) override;
|
||||
|
||||
virtual void graphics(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx) override;
|
||||
|
||||
virtual void postpass(rhi::Rhi& rhi) override;
|
||||
|
||||
rhi::Handle<rhi::Texture> screen_texture() const noexcept { return screen_texture_; }
|
||||
};
|
||||
|
||||
} // namespace srb2::hwr2
|
||||
|
||||
#endif // __SRB2_HWR2_PASS_SOFTWARE_HPP__
|
||||
#endif // __SRB2_HWR2_PASS_SOFTWARE_HPP_
|
||||
|
|
|
|||
954
src/hwr2/pass_twodee.cpp
Normal file
954
src/hwr2/pass_twodee.cpp
Normal file
|
|
@ -0,0 +1,954 @@
|
|||
// 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_twodee.hpp"
|
||||
|
||||
#include <unordered_set>
|
||||
|
||||
#include <stb_rect_pack.h>
|
||||
|
||||
#include "../r_patch.h"
|
||||
#include "../v_video.h"
|
||||
#include "../z_zone.h"
|
||||
|
||||
using namespace srb2;
|
||||
using namespace srb2::hwr2;
|
||||
using namespace srb2::rhi;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct AtlasEntry
|
||||
{
|
||||
uint32_t x;
|
||||
uint32_t y;
|
||||
uint32_t w;
|
||||
uint32_t h;
|
||||
|
||||
uint32_t trim_x;
|
||||
uint32_t trim_y;
|
||||
uint32_t orig_w;
|
||||
uint32_t orig_h;
|
||||
};
|
||||
|
||||
struct Atlas
|
||||
{
|
||||
Atlas() = default;
|
||||
Atlas(Atlas&&) = default;
|
||||
|
||||
Handle<Texture> tex;
|
||||
uint32_t tex_width;
|
||||
uint32_t tex_height;
|
||||
std::unordered_map<const patch_t*, AtlasEntry> entries;
|
||||
|
||||
std::unique_ptr<stbrp_context> rp_ctx {nullptr};
|
||||
std::unique_ptr<stbrp_node[]> rp_nodes {nullptr};
|
||||
|
||||
Atlas& operator=(Atlas&&) = default;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
struct srb2::hwr2::TwodeePassData
|
||||
{
|
||||
Handle<Texture> default_tex;
|
||||
Handle<Texture> palette_tex;
|
||||
Handle<Texture> default_colormap_tex;
|
||||
std::vector<Atlas> patch_atlases;
|
||||
std::unordered_map<const patch_t*, size_t> patch_lookup;
|
||||
std::vector<const patch_t*> patches_to_upload;
|
||||
std::unordered_map<const uint8_t*, Handle<Texture>> colormaps;
|
||||
std::vector<const uint8_t*> colormaps_to_upload;
|
||||
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;
|
||||
|
||||
static constexpr const uint32_t kVboInitSize = 32768;
|
||||
static constexpr const uint32_t kIboInitSize = 4096;
|
||||
|
||||
static Rect trimmed_patch_dim(const patch_t* patch);
|
||||
|
||||
static void create_atlas(Rhi& rhi, TwodeePassData& pass_data)
|
||||
{
|
||||
Atlas new_atlas;
|
||||
new_atlas.tex = rhi.create_texture({TextureFormat::kLuminanceAlpha, 2048, 2048});
|
||||
new_atlas.tex_width = 2048;
|
||||
new_atlas.tex_height = 2048;
|
||||
new_atlas.rp_ctx = std::make_unique<stbrp_context>();
|
||||
new_atlas.rp_nodes = std::make_unique<stbrp_node[]>(4096);
|
||||
for (size_t i = 0; i < 4096; i++)
|
||||
{
|
||||
new_atlas.rp_nodes[i] = {};
|
||||
}
|
||||
stbrp_init_target(new_atlas.rp_ctx.get(), 2048, 2048, new_atlas.rp_nodes.get(), 4096);
|
||||
// it is CRITICALLY important that the atlas is MOVED, not COPIED, otherwise the node ptrs will be broken
|
||||
pass_data.patch_atlases.push_back(std::move(new_atlas));
|
||||
}
|
||||
|
||||
static void pack_patches(Rhi& rhi, TwodeePassData& pass_data, tcb::span<const patch_t*> patches)
|
||||
{
|
||||
// Prepare stbrp rects for patches to be loaded.
|
||||
std::vector<stbrp_rect> rects;
|
||||
for (size_t i = 0; i < patches.size(); i++)
|
||||
{
|
||||
const patch_t* patch = patches[i];
|
||||
Rect trimmed_rect = trimmed_patch_dim(patch);
|
||||
stbrp_rect rect {};
|
||||
rect.id = i;
|
||||
rect.w = trimmed_rect.w;
|
||||
rect.h = trimmed_rect.h;
|
||||
rects.push_back(std::move(rect));
|
||||
}
|
||||
|
||||
while (rects.size() > 0)
|
||||
{
|
||||
if (pass_data.patch_atlases.size() == 0)
|
||||
{
|
||||
create_atlas(rhi, pass_data);
|
||||
}
|
||||
|
||||
for (size_t atlas_index = 0; atlas_index < pass_data.patch_atlases.size(); atlas_index++)
|
||||
{
|
||||
auto& atlas = pass_data.patch_atlases[atlas_index];
|
||||
|
||||
stbrp_pack_rects(atlas.rp_ctx.get(), rects.data(), rects.size());
|
||||
for (auto itr = rects.begin(); itr != rects.end();)
|
||||
{
|
||||
auto& rect = *itr;
|
||||
if (rect.was_packed)
|
||||
{
|
||||
AtlasEntry entry;
|
||||
const patch_t* patch = patches[rect.id];
|
||||
// TODO prevent unnecessary recalculation of trim?
|
||||
Rect trimmed_rect = trimmed_patch_dim(patch);
|
||||
entry.x = static_cast<uint32_t>(rect.x);
|
||||
entry.y = static_cast<uint32_t>(rect.y);
|
||||
entry.w = static_cast<uint32_t>(rect.w);
|
||||
entry.h = static_cast<uint32_t>(rect.h);
|
||||
entry.trim_x = static_cast<uint32_t>(trimmed_rect.x);
|
||||
entry.trim_y = static_cast<uint32_t>(trimmed_rect.y);
|
||||
entry.orig_w = static_cast<uint32_t>(patch->width);
|
||||
entry.orig_h = static_cast<uint32_t>(patch->height);
|
||||
atlas.entries.insert_or_assign(patch, std::move(entry));
|
||||
pass_data.patch_lookup.insert_or_assign(patch, atlas_index);
|
||||
pass_data.patches_to_upload.push_back(patch);
|
||||
rects.erase(itr);
|
||||
continue;
|
||||
}
|
||||
++itr;
|
||||
}
|
||||
|
||||
// If we still have rects to pack, and we're at the last atlas, create another atlas.
|
||||
// TODO This could end up in an infinite loop if the patches are bigger than an atlas. Such patches need to
|
||||
// be loaded as individual RHI textures instead.
|
||||
if (atlas_index == pass_data.patch_atlases.size() - 1 && rects.size() > 0)
|
||||
{
|
||||
create_atlas(rhi, pass_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @brief Derive the subrect of the given patch with empty columns and rows excluded.
|
||||
static Rect trimmed_patch_dim(const patch_t* patch)
|
||||
{
|
||||
bool minx_found = false;
|
||||
int32_t minx = 0;
|
||||
int32_t maxx = 0;
|
||||
int32_t miny = patch->height;
|
||||
int32_t maxy = 0;
|
||||
for (int32_t x = 0; x < patch->width; x++)
|
||||
{
|
||||
const int32_t columnofs = patch->columnofs[x];
|
||||
const column_t* column = reinterpret_cast<const column_t*>(patch->columns + columnofs);
|
||||
|
||||
// If the first pole is empty (topdelta = 255), there are no pixels in this column
|
||||
if (!minx_found && column->topdelta == 0xFF)
|
||||
{
|
||||
// Thus, the minx is at least one higher than the current column.
|
||||
minx = x + 1;
|
||||
continue;
|
||||
}
|
||||
minx_found = true;
|
||||
|
||||
if (minx_found && column->topdelta != 0xFF)
|
||||
{
|
||||
maxx = x;
|
||||
}
|
||||
|
||||
miny = std::min(static_cast<int32_t>(column->topdelta), miny);
|
||||
|
||||
int32_t prevdelta = 0;
|
||||
int32_t topdelta = 0;
|
||||
while (column->topdelta != 0xFF)
|
||||
{
|
||||
topdelta = column->topdelta;
|
||||
|
||||
// Tall patches hack
|
||||
if (topdelta <= prevdelta)
|
||||
{
|
||||
topdelta += prevdelta;
|
||||
}
|
||||
prevdelta = topdelta;
|
||||
|
||||
maxy = std::max(topdelta + column->length, maxy);
|
||||
|
||||
column = reinterpret_cast<const column_t*>(reinterpret_cast<const uint8_t*>(column) + column->length + 4);
|
||||
}
|
||||
}
|
||||
|
||||
maxx += 1;
|
||||
maxx = std::max(minx, maxx);
|
||||
maxy = std::max(miny, maxy);
|
||||
|
||||
return {minx, miny, static_cast<uint32_t>(maxx - minx), static_cast<uint32_t>(maxy - miny)};
|
||||
}
|
||||
|
||||
static void convert_patch_to_trimmed_rg8_pixels(const patch_t* patch, std::vector<uint8_t>& out)
|
||||
{
|
||||
Rect trimmed_rect = trimmed_patch_dim(patch);
|
||||
if (trimmed_rect.w % 2 > 0)
|
||||
{
|
||||
// In order to force 4-byte row alignment, an extra column is added to the image data.
|
||||
// Look up GL_UNPACK_ALIGNMENT (which defaults to 4 bytes)
|
||||
trimmed_rect.w += 1;
|
||||
}
|
||||
out.clear();
|
||||
// 2 bytes per pixel; 1 for the color index, 1 for the alpha. (RG8)
|
||||
out.resize(trimmed_rect.w * trimmed_rect.h * 2, 0);
|
||||
for (int32_t x = 0; x < static_cast<int32_t>(trimmed_rect.w) && x < (patch->width - trimmed_rect.x); x++)
|
||||
{
|
||||
const int32_t columnofs = patch->columnofs[x + trimmed_rect.x];
|
||||
const column_t* column = reinterpret_cast<const column_t*>(patch->columns + columnofs);
|
||||
|
||||
int32_t prevdelta = 0;
|
||||
int32_t topdelta = 0;
|
||||
while (column->topdelta != 0xFF)
|
||||
{
|
||||
topdelta = column->topdelta;
|
||||
// prevdelta is used to implement tall patches hack
|
||||
if (topdelta <= prevdelta)
|
||||
{
|
||||
topdelta += prevdelta;
|
||||
}
|
||||
|
||||
prevdelta = topdelta;
|
||||
const uint8_t* source = reinterpret_cast<const uint8_t*>(column) + 3;
|
||||
int32_t count = column->length; // is this byte order safe...?
|
||||
|
||||
for (int32_t i = 0; i < count; i++)
|
||||
{
|
||||
int32_t output_y = topdelta + i - trimmed_rect.y;
|
||||
if (output_y < 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (output_y >= static_cast<int32_t>(trimmed_rect.h))
|
||||
{
|
||||
break;
|
||||
}
|
||||
size_t pixel_index = (output_y * trimmed_rect.w + x) * 2;
|
||||
out[pixel_index + 0] = source[i]; // index in luminance/red channel
|
||||
out[pixel_index + 1] = 0xFF; // alpha/green value of 1
|
||||
}
|
||||
column = reinterpret_cast<const column_t*>(reinterpret_cast<const uint8_t*>(column) + column->length + 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static TwodeePipelineKey pipeline_key_for_cmd(const Draw2dCmd& cmd)
|
||||
{
|
||||
return {hwr2::get_blend_mode(cmd), hwr2::is_draw_lines(cmd)};
|
||||
}
|
||||
|
||||
static PipelineDesc make_pipeline_desc(TwodeePipelineKey key)
|
||||
{
|
||||
constexpr const VertexInputDesc kTwodeeVertexInput = {
|
||||
{{sizeof(TwodeeVertex)}},
|
||||
{{VertexAttributeName::kPosition, 0, 0},
|
||||
{VertexAttributeName::kTexCoord0, 0, 12},
|
||||
{VertexAttributeName::kColor, 0, 20}}};
|
||||
BlendDesc blend_desc;
|
||||
switch (key.blend)
|
||||
{
|
||||
case Draw2dBlend::kModulate:
|
||||
blend_desc.source_factor_color = BlendFactor::kSourceAlpha;
|
||||
blend_desc.dest_factor_color = BlendFactor::kOneMinusSourceAlpha;
|
||||
blend_desc.color_function = BlendFunction::kAdd;
|
||||
blend_desc.source_factor_alpha = BlendFactor::kOne;
|
||||
blend_desc.dest_factor_alpha = BlendFactor::kOneMinusSourceAlpha;
|
||||
blend_desc.alpha_function = BlendFunction::kAdd;
|
||||
break;
|
||||
case Draw2dBlend::kAdditive:
|
||||
blend_desc.source_factor_color = BlendFactor::kSourceAlpha;
|
||||
blend_desc.dest_factor_color = BlendFactor::kOne;
|
||||
blend_desc.color_function = BlendFunction::kAdd;
|
||||
blend_desc.source_factor_alpha = BlendFactor::kOne;
|
||||
blend_desc.dest_factor_alpha = BlendFactor::kOneMinusSourceAlpha;
|
||||
blend_desc.alpha_function = BlendFunction::kAdd;
|
||||
break;
|
||||
case Draw2dBlend::kSubtractive:
|
||||
blend_desc.source_factor_color = BlendFactor::kSourceAlpha;
|
||||
blend_desc.dest_factor_color = BlendFactor::kOne;
|
||||
blend_desc.color_function = BlendFunction::kSubtract;
|
||||
blend_desc.source_factor_alpha = BlendFactor::kOne;
|
||||
blend_desc.dest_factor_alpha = BlendFactor::kOneMinusSourceAlpha;
|
||||
blend_desc.alpha_function = BlendFunction::kAdd;
|
||||
break;
|
||||
case Draw2dBlend::kReverseSubtractive:
|
||||
blend_desc.source_factor_color = BlendFactor::kSourceAlpha;
|
||||
blend_desc.dest_factor_color = BlendFactor::kOne;
|
||||
blend_desc.color_function = BlendFunction::kReverseSubtract;
|
||||
blend_desc.source_factor_alpha = BlendFactor::kOne;
|
||||
blend_desc.dest_factor_alpha = BlendFactor::kOneMinusSourceAlpha;
|
||||
blend_desc.alpha_function = BlendFunction::kAdd;
|
||||
break;
|
||||
case Draw2dBlend::kInvertDest:
|
||||
blend_desc.source_factor_color = BlendFactor::kOne;
|
||||
blend_desc.dest_factor_color = BlendFactor::kOne;
|
||||
blend_desc.color_function = BlendFunction::kSubtract;
|
||||
blend_desc.source_factor_alpha = BlendFactor::kZero;
|
||||
blend_desc.dest_factor_alpha = BlendFactor::kDestAlpha;
|
||||
blend_desc.alpha_function = BlendFunction::kAdd;
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
PipelineProgram::kUnshadedPaletted,
|
||||
kTwodeeVertexInput,
|
||||
{{{{UniformName::kProjection}},
|
||||
{{UniformName::kModelView, UniformName::kTexCoord0Transform, UniformName::kSampler0IsIndexedAlpha}}}},
|
||||
{{SamplerName::kSampler0, SamplerName::kSampler1, SamplerName::kSampler2}},
|
||||
std::nullopt,
|
||||
{PixelFormat::kRGBA8, blend_desc, {true, true, true, true}},
|
||||
key.lines ? PrimitiveType::kLines : PrimitiveType::kTriangles,
|
||||
CullMode::kNone,
|
||||
FaceWinding::kCounterClockwise,
|
||||
{0.f, 0.f, 0.f, 1.f}};
|
||||
}
|
||||
|
||||
static void rewrite_patch_quad_vertices(Draw2dList& list, const Draw2dPatchQuad& cmd, TwodeePassData* data)
|
||||
{
|
||||
// Patch quads are clipped according to the patch's atlas entry
|
||||
if (cmd.patch == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::size_t atlas_index = data->patch_lookup[cmd.patch];
|
||||
auto& atlas = data->patch_atlases[atlas_index];
|
||||
auto& entry = atlas.entries[cmd.patch];
|
||||
|
||||
// Rewrite the vertex data completely.
|
||||
// The UVs of the trimmed patch in atlas UV space.
|
||||
const float atlas_umin = static_cast<float>(entry.x) / atlas.tex_width;
|
||||
const float atlas_umax = static_cast<float>(entry.x + entry.w) / atlas.tex_width;
|
||||
const float atlas_vmin = static_cast<float>(entry.y) / atlas.tex_height;
|
||||
const float atlas_vmax = static_cast<float>(entry.y + entry.h) / atlas.tex_height;
|
||||
|
||||
// The UVs of the trimmed patch in untrimmed UV space.
|
||||
// The command's UVs are in untrimmed UV space.
|
||||
const float trim_umin = static_cast<float>(entry.trim_x) / entry.orig_w;
|
||||
const float trim_umax = static_cast<float>(entry.trim_x + entry.w) / entry.orig_w;
|
||||
const float trim_vmin = static_cast<float>(entry.trim_y) / entry.orig_h;
|
||||
const float trim_vmax = static_cast<float>(entry.trim_y + entry.h) / entry.orig_h;
|
||||
|
||||
// Calculate positions
|
||||
const float cmd_xrange = cmd.xmax - cmd.xmin;
|
||||
const float cmd_yrange = cmd.ymax - cmd.ymin;
|
||||
const float clipped_xmin = cmd.clip ? std::clamp(cmd.xmin, cmd.clip_xmin, cmd.clip_xmax) : cmd.xmin;
|
||||
const float clipped_xmax = cmd.clip ? std::clamp(cmd.xmax, cmd.clip_xmin, cmd.clip_xmax) : cmd.xmax;
|
||||
const float clipped_ymin = cmd.clip ? std::clamp(cmd.ymin, cmd.clip_ymin, cmd.clip_ymax) : cmd.ymin;
|
||||
const float clipped_ymax = cmd.clip ? std::clamp(cmd.ymax, cmd.clip_ymin, cmd.clip_ymax) : cmd.ymax;
|
||||
const float trimmed_xmin = cmd.xmin + trim_umin * cmd_xrange;
|
||||
const float trimmed_xmax = cmd.xmax - (1.f - trim_umax) * cmd_xrange;
|
||||
const float trimmed_ymin = cmd.ymin + trim_vmin * cmd_yrange;
|
||||
const float trimmed_ymax = cmd.ymax - (1.f - trim_vmax) * cmd_yrange;
|
||||
const float trimmed_xrange = trimmed_xmax - trimmed_xmin;
|
||||
const float trimmed_yrange = trimmed_ymax - trimmed_ymin;
|
||||
float clipped_trimmed_xmin = std::max(clipped_xmin, trimmed_xmin);
|
||||
float clipped_trimmed_xmax = std::min(clipped_xmax, trimmed_xmax);
|
||||
float clipped_trimmed_ymin = std::max(clipped_ymin, trimmed_ymin);
|
||||
float clipped_trimmed_ymax = std::min(clipped_ymax, trimmed_ymax);
|
||||
clipped_trimmed_xmin = std::min(clipped_trimmed_xmin, clipped_trimmed_xmax);
|
||||
clipped_trimmed_ymin = std::min(clipped_trimmed_ymin, clipped_trimmed_ymax);
|
||||
|
||||
// Calculate UVs
|
||||
// Start from trimmed dimensions as 0..1 and clip UVs based on that
|
||||
// UVs in trimmed UV space (if clipped_xmin = trimmed_xmin, it'll be 0)
|
||||
float clipped_umin;
|
||||
float clipped_umax;
|
||||
float clipped_vmin;
|
||||
float clipped_vmax;
|
||||
|
||||
if (cmd.flip)
|
||||
{
|
||||
clipped_umin = std::max(0.f, 1.f - (clipped_trimmed_xmin - trimmed_xmin) / trimmed_xrange);
|
||||
clipped_umax = std::min(1.f, (trimmed_xmax - clipped_trimmed_xmax) / trimmed_xrange);
|
||||
}
|
||||
else
|
||||
{
|
||||
clipped_umin = std::min(1.f, (clipped_trimmed_xmin - trimmed_xmin) / trimmed_xrange);
|
||||
clipped_umax = std::max(0.f, 1.f - (trimmed_xmax - clipped_trimmed_xmax) / trimmed_xrange);
|
||||
}
|
||||
|
||||
if (cmd.vflip)
|
||||
{
|
||||
clipped_vmin = std::max(0.f, 1.f - (clipped_trimmed_ymin - trimmed_ymin) / trimmed_yrange);
|
||||
clipped_vmax = std::min(1.f, (trimmed_ymax - clipped_trimmed_ymax) / trimmed_yrange);
|
||||
}
|
||||
else
|
||||
{
|
||||
clipped_vmin = std::min(1.f, 0.f + (clipped_trimmed_ymin - trimmed_ymin) / trimmed_yrange);
|
||||
clipped_vmax = std::max(0.f, 1.f - (trimmed_ymax - clipped_trimmed_ymax) / trimmed_yrange);
|
||||
}
|
||||
|
||||
// convert from trimmed UV space to atlas space
|
||||
clipped_umin = (atlas_umax - atlas_umin) * clipped_umin + atlas_umin;
|
||||
clipped_umax = (atlas_umax - atlas_umin) * clipped_umax + atlas_umin;
|
||||
clipped_vmin = (atlas_vmax - atlas_vmin) * clipped_vmin + atlas_vmin;
|
||||
clipped_vmax = (atlas_vmax - atlas_vmin) * clipped_vmax + atlas_vmin;
|
||||
|
||||
std::size_t vtx_offs = cmd.begin_index;
|
||||
// Vertex order is always min/min, max/min, max/max, min/max
|
||||
list.vertices[vtx_offs + 0].x = clipped_trimmed_xmin;
|
||||
list.vertices[vtx_offs + 0].y = clipped_trimmed_ymin;
|
||||
list.vertices[vtx_offs + 0].u = clipped_umin;
|
||||
list.vertices[vtx_offs + 0].v = clipped_vmin;
|
||||
list.vertices[vtx_offs + 1].x = clipped_trimmed_xmax;
|
||||
list.vertices[vtx_offs + 1].y = clipped_trimmed_ymin;
|
||||
list.vertices[vtx_offs + 1].u = clipped_umax;
|
||||
list.vertices[vtx_offs + 1].v = clipped_vmin;
|
||||
list.vertices[vtx_offs + 2].x = clipped_trimmed_xmax;
|
||||
list.vertices[vtx_offs + 2].y = clipped_trimmed_ymax;
|
||||
list.vertices[vtx_offs + 2].u = clipped_umax;
|
||||
list.vertices[vtx_offs + 2].v = clipped_vmax;
|
||||
list.vertices[vtx_offs + 3].x = clipped_trimmed_xmin;
|
||||
list.vertices[vtx_offs + 3].y = clipped_trimmed_ymax;
|
||||
list.vertices[vtx_offs + 3].u = clipped_umin;
|
||||
list.vertices[vtx_offs + 3].v = clipped_vmax;
|
||||
}
|
||||
|
||||
void TwodeePass::prepass(Rhi& rhi)
|
||||
{
|
||||
if (!ctx_ || !data_)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (data_->pipelines.size() == 0)
|
||||
{
|
||||
TwodeePipelineKey modulate_tris = {Draw2dBlend::kModulate, false};
|
||||
TwodeePipelineKey additive_tris = {Draw2dBlend::kAdditive, false};
|
||||
TwodeePipelineKey subtractive_tris = {Draw2dBlend::kSubtractive, false};
|
||||
TwodeePipelineKey revsubtractive_tris = {Draw2dBlend::kReverseSubtractive, false};
|
||||
TwodeePipelineKey invertdest_tris = {Draw2dBlend::kInvertDest, false};
|
||||
TwodeePipelineKey modulate_lines = {Draw2dBlend::kModulate, true};
|
||||
TwodeePipelineKey additive_lines = {Draw2dBlend::kAdditive, true};
|
||||
TwodeePipelineKey subtractive_lines = {Draw2dBlend::kSubtractive, true};
|
||||
TwodeePipelineKey revsubtractive_lines = {Draw2dBlend::kReverseSubtractive, true};
|
||||
TwodeePipelineKey invertdest_lines = {Draw2dBlend::kInvertDest, true};
|
||||
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({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))});
|
||||
}
|
||||
|
||||
if (!data_->default_tex)
|
||||
{
|
||||
data_->default_tex = rhi.create_texture({TextureFormat::kLuminanceAlpha, 2, 1});
|
||||
data_->upload_default_tex = true;
|
||||
}
|
||||
if (!data_->palette_tex)
|
||||
{
|
||||
data_->palette_tex = rhi.create_texture({TextureFormat::kRGBA, 256, 1});
|
||||
}
|
||||
if (!data_->default_colormap_tex)
|
||||
{
|
||||
data_->default_colormap_tex = rhi.create_texture({TextureFormat::kLuminance, 256, 1});
|
||||
data_->upload_default_tex = true;
|
||||
}
|
||||
if (!render_pass_)
|
||||
{
|
||||
render_pass_ = rhi.create_render_pass(
|
||||
{std::nullopt, PixelFormat::kRGBA8, AttachmentLoadOp::kLoad, AttachmentStoreOp::kStore}
|
||||
);
|
||||
}
|
||||
|
||||
// Check for patches that are being freed after this frame. Those patches must be present in the atlases for this
|
||||
// frame, but all atlases need to be cleared and rebuilt on next call to prepass.
|
||||
// This is based on the assumption that patches are very rarely freed during runtime; occasionally repacking the
|
||||
// atlases to free up space from patches that will never be referenced again is acceptable.
|
||||
if (rebuild_atlases_)
|
||||
{
|
||||
for (auto& atlas : data_->patch_atlases)
|
||||
{
|
||||
rhi.destroy_texture(atlas.tex);
|
||||
}
|
||||
data_->patch_atlases.clear();
|
||||
data_->patch_lookup.clear();
|
||||
rebuild_atlases_ = false;
|
||||
}
|
||||
|
||||
if (data_->patch_atlases.size() > 2)
|
||||
{
|
||||
// Rebuild the atlases next frame because we have too many patches in the atlas cache.
|
||||
rebuild_atlases_ = true;
|
||||
}
|
||||
|
||||
// Stage 1 - command list patch detection
|
||||
std::unordered_set<const patch_t*> found_patches;
|
||||
std::unordered_set<const uint8_t*> found_colormaps;
|
||||
for (const auto& list : *ctx_)
|
||||
{
|
||||
for (const auto& cmd : list.cmds)
|
||||
{
|
||||
auto visitor = srb2::Overload {
|
||||
[&](const Draw2dPatchQuad& cmd)
|
||||
{
|
||||
if (cmd.patch != nullptr)
|
||||
{
|
||||
found_patches.insert(cmd.patch);
|
||||
}
|
||||
if (cmd.colormap != nullptr)
|
||||
{
|
||||
found_colormaps.insert(cmd.colormap);
|
||||
}
|
||||
},
|
||||
[&](const Draw2dVertices& cmd) {}};
|
||||
std::visit(visitor, cmd);
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_set<const patch_t*> patch_cache_hits;
|
||||
std::unordered_set<const patch_t*> patch_cache_misses;
|
||||
for (auto patch : found_patches)
|
||||
{
|
||||
if (data_->patch_lookup.find(patch) != data_->patch_lookup.end())
|
||||
{
|
||||
patch_cache_hits.insert(patch);
|
||||
}
|
||||
else
|
||||
{
|
||||
patch_cache_misses.insert(patch);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto colormap : found_colormaps)
|
||||
{
|
||||
if (data_->colormaps.find(colormap) == data_->colormaps.end())
|
||||
{
|
||||
Handle<Texture> colormap_tex = rhi.create_texture({TextureFormat::kLuminance, 256, 1});
|
||||
data_->colormaps.insert({colormap, colormap_tex});
|
||||
}
|
||||
|
||||
data_->colormaps_to_upload.push_back(colormap);
|
||||
}
|
||||
|
||||
// Stage 2 - pack rects into atlases
|
||||
std::vector<const patch_t*> patches_to_pack(patch_cache_misses.begin(), patch_cache_misses.end());
|
||||
pack_patches(rhi, *data_, patches_to_pack);
|
||||
// We now know what patches need to be uploaded.
|
||||
|
||||
size_t list_index = 0;
|
||||
for (auto& list : *ctx_)
|
||||
{
|
||||
Handle<Buffer> vbo;
|
||||
uint32_t vertex_data_size = tcb::as_bytes(tcb::span(list.vertices)).size();
|
||||
uint32_t needed_vbo_size = std::max(
|
||||
kVboInitSize,
|
||||
((static_cast<uint32_t>(vertex_data_size) + kVboInitSize - 1) / kVboInitSize) * kVboInitSize
|
||||
);
|
||||
|
||||
// Get the existing buffer objects. Recreate them if they don't exist, or needs to be bigger.
|
||||
|
||||
if (list_index >= vbos_.size())
|
||||
{
|
||||
vbo = rhi.create_buffer({needed_vbo_size, BufferType::kVertexBuffer, BufferUsage::kDynamic});
|
||||
vbos_.push_back({vbo, needed_vbo_size});
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t existing_size = std::get<1>(vbos_[list_index]);
|
||||
if (needed_vbo_size > existing_size)
|
||||
{
|
||||
rhi.destroy_buffer(std::get<0>(vbos_[list_index]));
|
||||
vbo = rhi.create_buffer({needed_vbo_size, BufferType::kVertexBuffer, BufferUsage::kDynamic});
|
||||
vbos_[list_index] = {vbo, needed_vbo_size};
|
||||
}
|
||||
vbo = std::get<0>(vbos_[list_index]);
|
||||
}
|
||||
|
||||
Handle<Buffer> ibo;
|
||||
uint32_t index_data_size = tcb::as_bytes(tcb::span(list.indices)).size();
|
||||
uint32_t needed_ibo_size = std::max(
|
||||
kIboInitSize,
|
||||
((static_cast<uint32_t>(index_data_size) + kIboInitSize - 1) / kIboInitSize) * kIboInitSize
|
||||
);
|
||||
|
||||
if (list_index >= ibos_.size())
|
||||
{
|
||||
ibo = rhi.create_buffer({needed_ibo_size, BufferType::kIndexBuffer, BufferUsage::kDynamic});
|
||||
ibos_.push_back({ibo, needed_ibo_size});
|
||||
}
|
||||
else
|
||||
{
|
||||
uint32_t existing_size = std::get<1>(ibos_[list_index]);
|
||||
if (needed_ibo_size > existing_size)
|
||||
{
|
||||
rhi.destroy_buffer(std::get<0>(ibos_[list_index]));
|
||||
ibo = rhi.create_buffer({needed_ibo_size, BufferType::kIndexBuffer, BufferUsage::kDynamic});
|
||||
ibos_[list_index] = {ibo, needed_ibo_size};
|
||||
}
|
||||
ibo = std::get<0>(ibos_[list_index]);
|
||||
}
|
||||
|
||||
// Create a merged command list
|
||||
MergedTwodeeCommandList merged_list;
|
||||
merged_list.vbo = vbo;
|
||||
merged_list.vbo_size = needed_vbo_size;
|
||||
merged_list.ibo = ibo;
|
||||
merged_list.ibo_size = needed_ibo_size;
|
||||
|
||||
MergedTwodeeCommand new_cmd;
|
||||
new_cmd.index_offset = 0;
|
||||
new_cmd.elements = 0;
|
||||
new_cmd.colormap = nullptr;
|
||||
// safety: a command list is required to have at least 1 command
|
||||
new_cmd.pipeline_key = pipeline_key_for_cmd(list.cmds[0]);
|
||||
merged_list.cmds.push_back(std::move(new_cmd));
|
||||
|
||||
for (auto& cmd : list.cmds)
|
||||
{
|
||||
auto& merged_cmd = *merged_list.cmds.rbegin();
|
||||
bool new_cmd_needed = false;
|
||||
TwodeePipelineKey pk = pipeline_key_for_cmd(cmd);
|
||||
new_cmd_needed = new_cmd_needed || (pk != merged_cmd.pipeline_key);
|
||||
|
||||
// We need to split the merged commands based on the kind of texture
|
||||
// Patches are converted to atlas texture indexes, which we've just packed the patch rects for
|
||||
// Flats are uploaded as individual textures.
|
||||
// TODO actually implement flat drawing
|
||||
auto tex_visitor = srb2::Overload {
|
||||
[&](const Draw2dPatchQuad& cmd)
|
||||
{
|
||||
if (cmd.patch == nullptr)
|
||||
{
|
||||
new_cmd_needed = new_cmd_needed || (merged_cmd.texture != std::nullopt);
|
||||
}
|
||||
else
|
||||
{
|
||||
size_t atlas_index = data_->patch_lookup[cmd.patch];
|
||||
typeof(merged_cmd.texture) atlas_index_texture = atlas_index;
|
||||
new_cmd_needed = new_cmd_needed || (merged_cmd.texture != atlas_index_texture);
|
||||
}
|
||||
|
||||
new_cmd_needed = new_cmd_needed || (merged_cmd.colormap != cmd.colormap);
|
||||
},
|
||||
[&](const Draw2dVertices& cmd)
|
||||
{
|
||||
if (cmd.flat_lump == LUMPERROR)
|
||||
{
|
||||
new_cmd_needed |= (merged_cmd.texture != std::nullopt);
|
||||
}
|
||||
else
|
||||
{
|
||||
typeof(merged_cmd.texture) flat_tex = MergedTwodeeCommandFlatTexture {cmd.flat_lump};
|
||||
new_cmd_needed |= (merged_cmd.texture != flat_tex);
|
||||
}
|
||||
|
||||
new_cmd_needed = new_cmd_needed || (merged_cmd.colormap != nullptr);
|
||||
}};
|
||||
std::visit(tex_visitor, cmd);
|
||||
|
||||
if (new_cmd_needed)
|
||||
{
|
||||
MergedTwodeeCommand the_new_one;
|
||||
the_new_one.index_offset = merged_cmd.index_offset + merged_cmd.elements;
|
||||
|
||||
// Map to the merged version of the texture variant. Yay...!
|
||||
auto tex_visitor_again = srb2::Overload {
|
||||
[&](const Draw2dPatchQuad& cmd)
|
||||
{
|
||||
if (cmd.patch != nullptr)
|
||||
{
|
||||
the_new_one.texture = data_->patch_lookup[cmd.patch];
|
||||
}
|
||||
else
|
||||
{
|
||||
the_new_one.texture = std::nullopt;
|
||||
}
|
||||
the_new_one.colormap = cmd.colormap;
|
||||
},
|
||||
[&](const Draw2dVertices& cmd)
|
||||
{
|
||||
if (cmd.flat_lump != LUMPERROR)
|
||||
{
|
||||
flat_manager_->find_or_create_indexed(rhi, cmd.flat_lump);
|
||||
typeof(the_new_one.texture) t = MergedTwodeeCommandFlatTexture {cmd.flat_lump};
|
||||
the_new_one.texture = t;
|
||||
}
|
||||
else
|
||||
{
|
||||
the_new_one.texture = std::nullopt;
|
||||
}
|
||||
|
||||
the_new_one.colormap = nullptr;
|
||||
}};
|
||||
std::visit(tex_visitor_again, cmd);
|
||||
the_new_one.pipeline_key = pipeline_key_for_cmd(cmd);
|
||||
merged_list.cmds.push_back(std::move(the_new_one));
|
||||
}
|
||||
|
||||
// There may or may not be a new current command; update its element count
|
||||
auto& new_merged_cmd = *merged_list.cmds.rbegin();
|
||||
// We know for sure that all commands in a command list have a contiguous range of elements in the IBO
|
||||
// So we can draw them in batch if the pipeline key and textures match
|
||||
new_merged_cmd.elements += hwr2::elements(cmd);
|
||||
|
||||
// Perform coordinate transformations
|
||||
{
|
||||
auto vtx_transform_visitor = srb2::Overload {
|
||||
[&](const Draw2dPatchQuad& cmd) { rewrite_patch_quad_vertices(list, cmd, data_.get()); },
|
||||
[&](const Draw2dVertices& cmd) {}};
|
||||
std::visit(vtx_transform_visitor, cmd);
|
||||
}
|
||||
}
|
||||
|
||||
cmd_lists_.push_back(std::move(merged_list));
|
||||
|
||||
list_index++;
|
||||
}
|
||||
}
|
||||
|
||||
void TwodeePass::transfer(Rhi& rhi, Handle<TransferContext> 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)));
|
||||
|
||||
std::array<uint8_t, 256> colormap_data;
|
||||
for (size_t i = 0; i < 256; i++)
|
||||
{
|
||||
colormap_data[i] = i;
|
||||
}
|
||||
rhi.update_texture(
|
||||
ctx,
|
||||
data_->default_colormap_tex,
|
||||
{0, 0, 256, 1},
|
||||
PixelFormat::kR8,
|
||||
tcb::as_bytes(tcb::span(colormap_data))
|
||||
);
|
||||
|
||||
data_->upload_default_tex = false;
|
||||
}
|
||||
|
||||
{
|
||||
// TODO share palette tex with software pass
|
||||
// Unfortunately, pMasterPalette must be swizzled to get a linear layout.
|
||||
// Maybe some adjustments to palette storage can make this a straight upload.
|
||||
std::array<byteColor_t, 256> palette_32;
|
||||
for (size_t i = 0; i < 256; i++)
|
||||
{
|
||||
palette_32[i] = pMasterPalette[i].s;
|
||||
}
|
||||
rhi.update_texture(
|
||||
ctx,
|
||||
data_->palette_tex,
|
||||
{0, 0, 256, 1},
|
||||
rhi::PixelFormat::kRGBA8,
|
||||
tcb::as_bytes(tcb::span(palette_32))
|
||||
);
|
||||
}
|
||||
|
||||
for (auto colormap : data_->colormaps_to_upload)
|
||||
{
|
||||
rhi.update_texture(
|
||||
ctx,
|
||||
data_->colormaps[colormap],
|
||||
{0, 0, 256, 1},
|
||||
rhi::PixelFormat::kR8,
|
||||
tcb::as_bytes(tcb::span(colormap, 256))
|
||||
);
|
||||
}
|
||||
data_->colormaps_to_upload.clear();
|
||||
|
||||
// Convert patches to RG8 textures and upload to atlas pages
|
||||
std::vector<uint8_t> patch_data;
|
||||
for (const patch_t* patch_to_upload : data_->patches_to_upload)
|
||||
{
|
||||
Atlas& atlas = data_->patch_atlases[data_->patch_lookup[patch_to_upload]];
|
||||
AtlasEntry& entry = atlas.entries[patch_to_upload];
|
||||
|
||||
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))
|
||||
);
|
||||
}
|
||||
data_->patches_to_upload.clear();
|
||||
|
||||
// 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& merged_list = cmd_lists_[i];
|
||||
auto& orig_list = *ctx_list_itr;
|
||||
|
||||
tcb::span<const std::byte> vertex_data = tcb::as_bytes(tcb::span(orig_list.vertices));
|
||||
tcb::span<const std::byte> index_data = tcb::as_bytes(tcb::span(orig_list.indices));
|
||||
rhi.update_buffer_contents(ctx, merged_list.vbo, 0, vertex_data);
|
||||
rhi.update_buffer_contents(ctx, merged_list.ibo, 0, index_data);
|
||||
|
||||
// Update the binding sets for each individual merged command
|
||||
VertexAttributeBufferBinding vbos[] = {{0, merged_list.vbo}};
|
||||
for (auto& mcmd : merged_list.cmds)
|
||||
{
|
||||
TextureBinding tx[3];
|
||||
auto tex_visitor = srb2::Overload {
|
||||
[&](size_t atlas_index)
|
||||
{
|
||||
Atlas& atlas = data_->patch_atlases[atlas_index];
|
||||
tx[0] = {SamplerName::kSampler0, atlas.tex};
|
||||
tx[1] = {SamplerName::kSampler1, data_->palette_tex};
|
||||
},
|
||||
[&](const MergedTwodeeCommandFlatTexture& tex)
|
||||
{
|
||||
Handle<Texture> th = flat_manager_->find_indexed(tex.lump);
|
||||
SRB2_ASSERT(th != kNullHandle);
|
||||
tx[0] = {SamplerName::kSampler0, th};
|
||||
tx[1] = {SamplerName::kSampler1, data_->palette_tex};
|
||||
}};
|
||||
if (mcmd.texture)
|
||||
{
|
||||
std::visit(tex_visitor, *mcmd.texture);
|
||||
}
|
||||
else
|
||||
{
|
||||
tx[0] = {SamplerName::kSampler0, data_->default_tex};
|
||||
tx[1] = {SamplerName::kSampler1, data_->palette_tex};
|
||||
}
|
||||
|
||||
const uint8_t* colormap = mcmd.colormap;
|
||||
Handle<Texture> colormap_h = data_->default_colormap_tex;
|
||||
if (colormap)
|
||||
{
|
||||
SRB2_ASSERT(data_->colormaps.find(colormap) != data_->colormaps.end());
|
||||
colormap_h = data_->colormaps[colormap];
|
||||
}
|
||||
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)});
|
||||
}
|
||||
|
||||
ctx_list_itr++;
|
||||
}
|
||||
|
||||
// Uniform sets
|
||||
std::array<UniformVariant, 1> g1_uniforms = {{
|
||||
// Projection
|
||||
std::array<std::array<float, 4>, 4> {
|
||||
{{2.f / vid.width, 0.f, 0.f, 0.f},
|
||||
{0.f, -2.f / vid.height, 0.f, 0.f},
|
||||
{0.f, 0.f, 1.f, 0.f},
|
||||
{-1.f, 1.f, 0.f, 1.f}}},
|
||||
}};
|
||||
std::array<UniformVariant, 3> g2_uniforms = {
|
||||
{// ModelView
|
||||
std::array<std::array<float, 4>, 4> {
|
||||
{{1.f, 0.f, 0.f, 0.f}, {0.f, 1.f, 0.f, 0.f}, {0.f, 0.f, 1.f, 0.f}, {0.f, 0.f, 0.f, 1.f}}},
|
||||
// Texcoord0 Transform
|
||||
std::array<std::array<float, 3>, 3> {{{1.f, 0.f, 0.f}, {0.f, 1.f, 0.f}, {0.f, 0.f, 1.f}}},
|
||||
// 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 rhi::Color 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);
|
||||
}
|
||||
|
||||
for (auto& list : cmd_lists_)
|
||||
{
|
||||
for (auto& cmd : list.cmds)
|
||||
{
|
||||
if (cmd.elements == 0)
|
||||
{
|
||||
// Don't do anything for 0-element commands
|
||||
// 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];
|
||||
rhi.bind_pipeline(ctx, pl);
|
||||
if (output_)
|
||||
{
|
||||
rhi.set_viewport(ctx, {0, 0, output_width_, output_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);
|
||||
rhi.bind_index_buffer(ctx, list.ibo);
|
||||
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();
|
||||
}
|
||||
116
src/hwr2/pass_twodee.hpp
Normal file
116
src/hwr2/pass_twodee.hpp
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
// 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_PASS_TWODEE_HPP__
|
||||
#define __SRB2_HWR2_PASS_TWODEE_HPP__
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include "../cxxutil.hpp"
|
||||
#include "pass.hpp"
|
||||
#include "pass_resource_managers.hpp"
|
||||
#include "twodee.hpp"
|
||||
|
||||
namespace srb2::hwr2
|
||||
{
|
||||
|
||||
class TwodeePass;
|
||||
|
||||
/// @brief Shared structures to allow multiple 2D instances to share the same atlases
|
||||
struct TwodeePassData;
|
||||
|
||||
/// @brief Hash map key for caching pipelines
|
||||
struct TwodeePipelineKey
|
||||
{
|
||||
Draw2dBlend blend;
|
||||
bool lines;
|
||||
|
||||
bool operator==(const TwodeePipelineKey& r) const noexcept { return !(blend != r.blend || lines != r.lines); }
|
||||
bool operator!=(const TwodeePipelineKey& r) const noexcept { return !(*this == r); }
|
||||
};
|
||||
|
||||
struct MergedTwodeeCommandFlatTexture
|
||||
{
|
||||
lumpnum_t lump;
|
||||
|
||||
bool operator==(const MergedTwodeeCommandFlatTexture& rhs) const noexcept { return lump == rhs.lump; }
|
||||
bool operator!=(const MergedTwodeeCommandFlatTexture& rhs) const noexcept { return !(*this == rhs); }
|
||||
};
|
||||
|
||||
struct MergedTwodeeCommand
|
||||
{
|
||||
TwodeePipelineKey pipeline_key = {};
|
||||
rhi::Handle<rhi::BindingSet> binding_set = {};
|
||||
std::optional<std::variant<size_t, MergedTwodeeCommandFlatTexture>> texture;
|
||||
const uint8_t* colormap;
|
||||
uint32_t index_offset = 0;
|
||||
uint32_t elements = 0;
|
||||
};
|
||||
|
||||
struct MergedTwodeeCommandList
|
||||
{
|
||||
rhi::Handle<rhi::Buffer> vbo {};
|
||||
uint32_t vbo_size = 0;
|
||||
rhi::Handle<rhi::Buffer> ibo {};
|
||||
uint32_t ibo_size = 0;
|
||||
|
||||
std::vector<MergedTwodeeCommand> cmds;
|
||||
};
|
||||
|
||||
std::shared_ptr<TwodeePassData> make_twodee_pass_data();
|
||||
|
||||
struct TwodeePass final : public Pass
|
||||
{
|
||||
Twodee* ctx_ = nullptr;
|
||||
std::variant<rhi::Handle<rhi::Texture>, rhi::Handle<rhi::Renderbuffer>> out_color_;
|
||||
|
||||
std::shared_ptr<TwodeePassData> data_;
|
||||
std::shared_ptr<FlatTextureManager> flat_manager_;
|
||||
rhi::Handle<rhi::UniformSet> us_1;
|
||||
rhi::Handle<rhi::UniformSet> us_2;
|
||||
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_;
|
||||
bool rebuild_atlases_ = false;
|
||||
rhi::Handle<rhi::RenderPass> render_pass_;
|
||||
rhi::Handle<rhi::Texture> output_;
|
||||
uint32_t output_width_ = 0;
|
||||
uint32_t output_height_ = 0;
|
||||
|
||||
TwodeePass();
|
||||
virtual ~TwodeePass();
|
||||
|
||||
virtual void prepass(rhi::Rhi& rhi) override;
|
||||
|
||||
virtual void transfer(rhi::Rhi& rhi, rhi::Handle<rhi::TransferContext> ctx) override;
|
||||
|
||||
virtual void graphics(rhi::Rhi& rhi, rhi::Handle<rhi::GraphicsContext> ctx) override;
|
||||
|
||||
virtual void postpass(rhi::Rhi& rhi) override;
|
||||
};
|
||||
|
||||
} // 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__
|
||||
114
src/hwr2/twodee.cpp
Normal file
114
src/hwr2/twodee.cpp
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
// 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 "twodee.hpp"
|
||||
|
||||
#include "../w_wad.h"
|
||||
|
||||
using namespace srb2;
|
||||
using namespace hwr2;
|
||||
|
||||
Twodee::Twodee() = default;
|
||||
Twodee::Twodee(const Twodee&) = default;
|
||||
Twodee::Twodee(Twodee&&) noexcept = default;
|
||||
Twodee& Twodee::operator=(const Twodee&) = default;
|
||||
|
||||
// Will the default move prevent the vectors from losing their allocations? I guess it depends on the STL impl.
|
||||
// It's probably worth optimizing around.
|
||||
Twodee& Twodee::operator=(Twodee&&) noexcept = default;
|
||||
|
||||
void Draw2dQuadBuilder::done()
|
||||
{
|
||||
if (ctx_.lists_.size() == 0)
|
||||
{
|
||||
ctx_.lists_.push_back({});
|
||||
}
|
||||
|
||||
if (ctx_.lists_.rbegin()->vertices.size() >= (Draw2dList::kMaxVertices - 4))
|
||||
{
|
||||
// The current draw list has too many vertices to fit this command
|
||||
ctx_.lists_.push_back({});
|
||||
}
|
||||
|
||||
auto& list = *ctx_.lists_.rbegin();
|
||||
quad_.begin_element = list.vertices.size();
|
||||
quad_.begin_index = list.vertices.size();
|
||||
|
||||
list.vertices.push_back({quad_.xmin, quad_.ymin, 0.f, 0, 0, quad_.r, quad_.g, quad_.b, quad_.a});
|
||||
list.vertices.push_back({quad_.xmax, quad_.ymin, 0.f, 1, 0, quad_.r, quad_.g, quad_.b, quad_.a});
|
||||
list.vertices.push_back({quad_.xmax, quad_.ymax, 0.f, 1, 1, quad_.r, quad_.g, quad_.b, quad_.a});
|
||||
list.vertices.push_back({quad_.xmin, quad_.ymax, 0.f, 0, 1, quad_.r, quad_.g, quad_.b, quad_.a});
|
||||
|
||||
list.indices.push_back(quad_.begin_element + 0);
|
||||
list.indices.push_back(quad_.begin_element + 1);
|
||||
list.indices.push_back(quad_.begin_element + 2);
|
||||
|
||||
list.indices.push_back(quad_.begin_element + 0);
|
||||
list.indices.push_back(quad_.begin_element + 2);
|
||||
list.indices.push_back(quad_.begin_element + 3);
|
||||
|
||||
list.cmds.push_back(quad_);
|
||||
}
|
||||
|
||||
void Draw2dVerticesBuilder::done()
|
||||
{
|
||||
if (ctx_.lists_.size() == 0)
|
||||
{
|
||||
ctx_.lists_.push_back({});
|
||||
}
|
||||
|
||||
if (ctx_.lists_.rbegin()->vertices.size() >= (Draw2dList::kMaxVertices - 4))
|
||||
{
|
||||
// The current draw list has too many vertices to fit this command
|
||||
ctx_.lists_.push_back({});
|
||||
}
|
||||
|
||||
auto& list = *ctx_.lists_.rbegin();
|
||||
tris_.begin_element = list.vertices.size();
|
||||
tris_.begin_index = list.indices.size();
|
||||
|
||||
if (verts_.empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::size_t i = 0;
|
||||
for (auto& vert : verts_)
|
||||
{
|
||||
list.vertices.push_back({vert[0], vert[1], 0, vert[2], vert[3], r_, g_, b_, a_});
|
||||
list.indices.push_back(tris_.begin_element + i);
|
||||
i++;
|
||||
}
|
||||
|
||||
list.cmds.push_back(tris_);
|
||||
}
|
||||
|
||||
Draw2dBlend srb2::hwr2::get_blend_mode(const Draw2dCmd& cmd) noexcept
|
||||
{
|
||||
auto visitor = srb2::Overload {
|
||||
[&](const Draw2dPatchQuad& cmd) { return cmd.blend; },
|
||||
[&](const Draw2dVertices& cmd) { return cmd.blend; }};
|
||||
return std::visit(visitor, cmd);
|
||||
}
|
||||
|
||||
bool srb2::hwr2::is_draw_lines(const Draw2dCmd& cmd) noexcept
|
||||
{
|
||||
auto visitor = srb2::Overload {
|
||||
[&](const Draw2dPatchQuad& cmd) { return false; },
|
||||
[&](const Draw2dVertices& cmd) { return cmd.lines; }};
|
||||
return std::visit(visitor, cmd);
|
||||
}
|
||||
|
||||
std::size_t srb2::hwr2::elements(const Draw2dCmd& cmd) noexcept
|
||||
{
|
||||
auto visitor = srb2::Overload {
|
||||
[&](const Draw2dPatchQuad& cmd) -> std::size_t { return 6; },
|
||||
[&](const Draw2dVertices& cmd) -> std::size_t { return cmd.elements; }};
|
||||
return std::visit(visitor, cmd);
|
||||
}
|
||||
280
src/hwr2/twodee.hpp
Normal file
280
src/hwr2/twodee.hpp
Normal file
|
|
@ -0,0 +1,280 @@
|
|||
// 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_TWODEE_HPP__
|
||||
#define __SRB2_HWR2_TWODEE_HPP__
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
#include <tcb/span.hpp>
|
||||
|
||||
#include "../cxxutil.hpp"
|
||||
#include "../doomtype.h"
|
||||
|
||||
namespace srb2::hwr2
|
||||
{
|
||||
|
||||
struct TwodeeVertex
|
||||
{
|
||||
float x;
|
||||
float y;
|
||||
float z;
|
||||
float u;
|
||||
float v;
|
||||
float r;
|
||||
float g;
|
||||
float b;
|
||||
float a;
|
||||
};
|
||||
|
||||
enum class Draw2dBlend
|
||||
{
|
||||
kModulate,
|
||||
kAdditive,
|
||||
kSubtractive,
|
||||
kReverseSubtractive,
|
||||
kInvertDest
|
||||
};
|
||||
|
||||
struct Draw2dPatchQuad
|
||||
{
|
||||
std::size_t begin_index = 0;
|
||||
std::size_t begin_element = 0;
|
||||
|
||||
// A null patch ptr means no patch is drawn
|
||||
const patch_t* patch = nullptr;
|
||||
const uint8_t* colormap = nullptr;
|
||||
Draw2dBlend blend;
|
||||
float r = 0.f;
|
||||
float g = 0.f;
|
||||
float b = 0.f;
|
||||
float a = 0.f;
|
||||
|
||||
// Size fields are made available to let the consumer modify the vertex data for optimization
|
||||
float xmin = 0.f;
|
||||
float ymin = 0.f;
|
||||
float xmax = 0.f;
|
||||
float ymax = 0.f;
|
||||
float clip_xmin = 0.f;
|
||||
float clip_xmax = 0.f;
|
||||
float clip_ymin = 0.f;
|
||||
float clip_ymax = 0.f;
|
||||
bool clip = false;
|
||||
bool flip = false;
|
||||
bool vflip = false;
|
||||
};
|
||||
|
||||
struct Draw2dVertices
|
||||
{
|
||||
std::size_t begin_index = 0;
|
||||
std::size_t begin_element = 0;
|
||||
std::size_t elements = 0;
|
||||
Draw2dBlend blend = Draw2dBlend::kModulate;
|
||||
lumpnum_t flat_lump = UINT32_MAX; // LUMPERROR but not loading w_wad.h from this header
|
||||
bool lines = false;
|
||||
};
|
||||
|
||||
using Draw2dCmd = std::variant<Draw2dPatchQuad, Draw2dVertices>;
|
||||
|
||||
Draw2dBlend get_blend_mode(const Draw2dCmd& cmd) noexcept;
|
||||
bool is_draw_lines(const Draw2dCmd& cmd) noexcept;
|
||||
std::size_t elements(const Draw2dCmd& cmd) noexcept;
|
||||
|
||||
struct Draw2dList
|
||||
{
|
||||
std::vector<TwodeeVertex> vertices;
|
||||
std::vector<uint16_t> indices;
|
||||
std::vector<Draw2dCmd> cmds;
|
||||
|
||||
static constexpr const std::size_t kMaxVertices = 65536;
|
||||
};
|
||||
|
||||
class Draw2dQuadBuilder;
|
||||
class Draw2dVerticesBuilder;
|
||||
|
||||
/// @brief Buffered 2D drawing context
|
||||
class Twodee
|
||||
{
|
||||
std::vector<Draw2dList> lists_;
|
||||
std::vector<TwodeeVertex> current_verts_;
|
||||
std::vector<uint16_t> current_indices_;
|
||||
|
||||
friend class Draw2dQuadBuilder;
|
||||
friend class Draw2dVerticesBuilder;
|
||||
|
||||
public:
|
||||
Twodee();
|
||||
Twodee(const Twodee&);
|
||||
Twodee(Twodee&&) noexcept;
|
||||
|
||||
Twodee& operator=(const Twodee&);
|
||||
Twodee& operator=(Twodee&&) noexcept;
|
||||
|
||||
Draw2dQuadBuilder begin_quad() noexcept;
|
||||
Draw2dVerticesBuilder begin_verts() noexcept;
|
||||
|
||||
typename std::vector<Draw2dList>::iterator begin() noexcept { return lists_.begin(); }
|
||||
typename std::vector<Draw2dList>::iterator end() noexcept { return lists_.end(); }
|
||||
typename std::vector<Draw2dList>::const_iterator begin() const noexcept { return lists_.cbegin(); }
|
||||
typename std::vector<Draw2dList>::const_iterator end() const noexcept { return lists_.cend(); }
|
||||
typename std::vector<Draw2dList>::const_iterator cbegin() const noexcept { return lists_.cbegin(); }
|
||||
typename std::vector<Draw2dList>::const_iterator cend() const noexcept { return lists_.cend(); }
|
||||
};
|
||||
|
||||
class Draw2dQuadBuilder
|
||||
{
|
||||
Draw2dPatchQuad quad_;
|
||||
Twodee& ctx_;
|
||||
|
||||
Draw2dQuadBuilder(Twodee& ctx) : quad_ {}, ctx_ {ctx} {}
|
||||
|
||||
friend class Twodee;
|
||||
|
||||
public:
|
||||
Draw2dQuadBuilder(const Draw2dQuadBuilder&) = delete;
|
||||
Draw2dQuadBuilder(Draw2dQuadBuilder&&) = default;
|
||||
Draw2dQuadBuilder& operator=(const Draw2dQuadBuilder&) = delete;
|
||||
Draw2dQuadBuilder& operator=(Draw2dQuadBuilder&&) = default;
|
||||
|
||||
Draw2dQuadBuilder& rect(float x, float y, float w, float h)
|
||||
{
|
||||
quad_.xmin = x;
|
||||
quad_.xmax = x + w;
|
||||
quad_.ymin = y;
|
||||
quad_.ymax = y + h;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Draw2dQuadBuilder& flip(bool flip)
|
||||
{
|
||||
quad_.flip = flip;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Draw2dQuadBuilder& vflip(bool vflip)
|
||||
{
|
||||
quad_.vflip = vflip;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Draw2dQuadBuilder& clip(float xmin, float ymin, float xmax, float ymax)
|
||||
{
|
||||
quad_.clip_xmin = xmin;
|
||||
quad_.clip_ymin = ymin;
|
||||
quad_.clip_xmax = xmax;
|
||||
quad_.clip_ymax = ymax;
|
||||
quad_.clip = true;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Draw2dQuadBuilder& color(float r, float g, float b, float a)
|
||||
{
|
||||
quad_.r = r;
|
||||
quad_.g = g;
|
||||
quad_.b = b;
|
||||
quad_.a = a;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Draw2dQuadBuilder& patch(const patch_t* patch)
|
||||
{
|
||||
quad_.patch = patch;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Draw2dQuadBuilder& blend(Draw2dBlend blend)
|
||||
{
|
||||
quad_.blend = blend;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Draw2dQuadBuilder& colormap(const uint8_t* colormap)
|
||||
{
|
||||
quad_.colormap = colormap;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void done();
|
||||
};
|
||||
|
||||
class Draw2dVerticesBuilder
|
||||
{
|
||||
Draw2dVertices tris_;
|
||||
Twodee& ctx_;
|
||||
std::vector<std::array<float, 4>> verts_;
|
||||
float r_ = 1.f;
|
||||
float g_ = 1.f;
|
||||
float b_ = 1.f;
|
||||
float a_ = 1.f;
|
||||
|
||||
Draw2dVerticesBuilder(Twodee& ctx) : tris_ {}, ctx_ {ctx} {}
|
||||
|
||||
friend class Twodee;
|
||||
|
||||
public:
|
||||
Draw2dVerticesBuilder(const Draw2dVerticesBuilder&) = delete;
|
||||
Draw2dVerticesBuilder(Draw2dVerticesBuilder&&) = default;
|
||||
Draw2dVerticesBuilder& operator=(const Draw2dVerticesBuilder&) = delete;
|
||||
Draw2dVerticesBuilder& operator=(Draw2dVerticesBuilder&&) = default;
|
||||
|
||||
Draw2dVerticesBuilder& vert(float x, float y, float u = 0, float v = 0)
|
||||
{
|
||||
verts_.push_back({x, y, u, v});
|
||||
tris_.elements += 1;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Draw2dVerticesBuilder& color(float r, float g, float b, float a)
|
||||
{
|
||||
r_ = r;
|
||||
g_ = g;
|
||||
b_ = b;
|
||||
a_ = a;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Draw2dVerticesBuilder& blend(Draw2dBlend blend)
|
||||
{
|
||||
tris_.blend = blend;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Draw2dVerticesBuilder& lines(bool lines)
|
||||
{
|
||||
tris_.lines = lines;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Draw2dVerticesBuilder& flat(lumpnum_t lump)
|
||||
{
|
||||
tris_.flat_lump = lump;
|
||||
return *this;
|
||||
}
|
||||
|
||||
void done();
|
||||
};
|
||||
|
||||
inline Draw2dQuadBuilder Twodee::begin_quad() noexcept
|
||||
{
|
||||
return Draw2dQuadBuilder(*this);
|
||||
}
|
||||
|
||||
inline Draw2dVerticesBuilder Twodee::begin_verts() noexcept
|
||||
{
|
||||
return Draw2dVerticesBuilder(*this);
|
||||
}
|
||||
|
||||
} // namespace srb2::hwr2
|
||||
|
||||
#endif // __SRB2_HWR2_TWODEE_HPP__
|
||||
|
|
@ -1,3 +1,12 @@
|
|||
// 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 "i_video.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
|
@ -7,24 +16,30 @@
|
|||
#include <imgui.h>
|
||||
|
||||
#include "cxxutil.hpp"
|
||||
#include "f_finale.h"
|
||||
#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_software.hpp"
|
||||
#include "hwr2/pass_twodee.hpp"
|
||||
#include "hwr2/twodee.hpp"
|
||||
#include "v_video.h"
|
||||
|
||||
// KILL THIS WHEN WE KILL OLD OGL SUPPORT PLEASE
|
||||
#include "d_netcmd.h" // kill
|
||||
#include "discord.h" // kill
|
||||
#include "doomstat.h" // kill
|
||||
#include "s_sound.h" // kill
|
||||
#include "sdl/ogl_sdl.h"
|
||||
#include "st_stuff.h" // kill
|
||||
#include "d_netcmd.h" // kill
|
||||
#include "doomstat.h" // kill
|
||||
#include "s_sound.h" // kill
|
||||
#include "discord.h" // kill
|
||||
|
||||
using namespace srb2;
|
||||
using namespace srb2::hwr2;
|
||||
using namespace srb2::rhi;
|
||||
|
||||
static SoftwareBlitPass g_sw_pass;
|
||||
static ImguiPass g_imgui_pass;
|
||||
static std::shared_ptr<PassManager> g_passmanager;
|
||||
|
||||
Handle<Rhi> srb2::sys::g_current_rhi = kNullHandle;
|
||||
|
||||
|
|
@ -48,8 +63,7 @@ static void finish_legacy_ogl_update()
|
|||
if (cv_ticrate.value)
|
||||
SCR_DisplayTicRate();
|
||||
|
||||
if (cv_showping.value && netgame &&
|
||||
( consoleplayer != serverplayer || ! server_lagless ))
|
||||
if (cv_showping.value && netgame && (consoleplayer != serverplayer || !server_lagless))
|
||||
{
|
||||
if (server_lagless)
|
||||
{
|
||||
|
|
@ -58,11 +72,8 @@ static void finish_legacy_ogl_update()
|
|||
}
|
||||
else
|
||||
{
|
||||
for (
|
||||
player = 1;
|
||||
player < MAXPLAYERS;
|
||||
player++
|
||||
){
|
||||
for (player = 1; player < MAXPLAYERS; player++)
|
||||
{
|
||||
if (D_IsPlayerHumanAndGaming(player))
|
||||
{
|
||||
SCR_DisplayLocalPing();
|
||||
|
|
@ -91,6 +102,154 @@ static void finish_legacy_ogl_update()
|
|||
}
|
||||
#endif
|
||||
|
||||
static std::shared_ptr<PassManager> build_pass_manager()
|
||||
{
|
||||
std::shared_ptr<PassManager> manager = std::make_shared<PassManager>();
|
||||
|
||||
std::shared_ptr<FramebufferManager> framebuffer_manager = std::make_shared<FramebufferManager>();
|
||||
std::shared_ptr<MainPaletteManager> palette_manager = std::make_shared<MainPaletteManager>();
|
||||
std::shared_ptr<FlatTextureManager> flat_texture_manager = std::make_shared<FlatTextureManager>();
|
||||
|
||||
std::shared_ptr<SoftwarePass> software_pass = std::make_shared<SoftwarePass>();
|
||||
std::shared_ptr<BlitRectPass> blit_sw_pass = std::make_shared<BlitRectPass>(palette_manager, true);
|
||||
std::shared_ptr<TwodeePass> twodee = std::make_shared<TwodeePass>();
|
||||
twodee->flat_manager_ = flat_texture_manager;
|
||||
twodee->data_ = make_twodee_pass_data();
|
||||
twodee->ctx_ = &g_2d;
|
||||
std::shared_ptr<BlitRectPass> pp_simple_blit_pass = std::make_shared<BlitRectPass>(false);
|
||||
std::shared_ptr<PostprocessWipePass> pp_wipe_pass = std::make_shared<PostprocessWipePass>();
|
||||
std::shared_ptr<ImguiPass> imgui_pass = std::make_shared<ImguiPass>();
|
||||
std::shared_ptr<BlitRectPass> final_composite_pass = std::make_shared<BlitRectPass>(true);
|
||||
|
||||
manager->insert("framebuffer_manager", framebuffer_manager);
|
||||
manager->insert("palette_manager", palette_manager);
|
||||
manager->insert("flat_texture_manager", flat_texture_manager);
|
||||
|
||||
manager->insert(
|
||||
"3d_prepare",
|
||||
[framebuffer_manager](PassManager& mgr, Rhi&)
|
||||
{
|
||||
const bool sw_enabled = rendermode == render_soft;
|
||||
|
||||
mgr.set_pass_enabled("software", !g_wipeskiprender && sw_enabled);
|
||||
mgr.set_pass_enabled("blit_sw_prepare", !g_wipeskiprender && sw_enabled);
|
||||
mgr.set_pass_enabled("blit_sw", !g_wipeskiprender && sw_enabled);
|
||||
},
|
||||
[framebuffer_manager](PassManager&, Rhi&)
|
||||
{
|
||||
if (!WipeInAction)
|
||||
{
|
||||
framebuffer_manager->swap_main();
|
||||
}
|
||||
}
|
||||
);
|
||||
manager->insert("software", software_pass);
|
||||
manager->insert(
|
||||
"blit_sw_prepare",
|
||||
[blit_sw_pass, software_pass, framebuffer_manager](PassManager&, Rhi&)
|
||||
{
|
||||
blit_sw_pass->set_texture(software_pass->screen_texture(), vid.width, vid.height);
|
||||
blit_sw_pass->set_output(framebuffer_manager->current_main_color(), vid.width, vid.height, false, false);
|
||||
}
|
||||
);
|
||||
manager->insert("blit_sw", blit_sw_pass);
|
||||
|
||||
manager->insert(
|
||||
"2d_prepare",
|
||||
[twodee, framebuffer_manager](PassManager& mgr, Rhi&)
|
||||
{
|
||||
twodee->output_ = framebuffer_manager->current_main_color();
|
||||
twodee->output_width_ = vid.width;
|
||||
twodee->output_height_ = vid.height;
|
||||
}
|
||||
);
|
||||
manager->insert("2d", twodee);
|
||||
|
||||
manager->insert(
|
||||
"pp_final_prepare",
|
||||
[](PassManager& mgr, Rhi&)
|
||||
{
|
||||
mgr.set_pass_enabled("pp_final_wipe_prepare", WipeInAction);
|
||||
mgr.set_pass_enabled("pp_final_wipe", WipeInAction);
|
||||
mgr.set_pass_enabled("pp_final_wipe_flip", WipeInAction);
|
||||
}
|
||||
);
|
||||
manager->insert(
|
||||
"pp_final_simple_blit_prepare",
|
||||
[pp_simple_blit_pass, framebuffer_manager](PassManager&, Rhi&)
|
||||
{
|
||||
Handle<Texture> color = framebuffer_manager->current_main_color();
|
||||
if (WipeInAction && !g_wipereverse)
|
||||
{
|
||||
// Non-reverse wipes are "fade-outs" from the previous frame.
|
||||
color = framebuffer_manager->previous_main_color();
|
||||
}
|
||||
pp_simple_blit_pass->set_texture(color, vid.width, vid.height);
|
||||
pp_simple_blit_pass
|
||||
->set_output(framebuffer_manager->current_post_color(), vid.width, vid.height, false, true);
|
||||
}
|
||||
);
|
||||
manager->insert("pp_final_simple_blit", pp_simple_blit_pass);
|
||||
manager->insert(
|
||||
"pp_final_simple_blit_flip",
|
||||
[framebuffer_manager](PassManager&, Rhi&) { framebuffer_manager->swap_post(); }
|
||||
);
|
||||
manager->insert(
|
||||
"pp_final_wipe_prepare",
|
||||
[pp_wipe_pass, framebuffer_manager](PassManager&, Rhi&)
|
||||
{
|
||||
pp_wipe_pass->set_source(framebuffer_manager->previous_post_color(), vid.width, vid.height);
|
||||
pp_wipe_pass->set_end(framebuffer_manager->current_main_color());
|
||||
pp_wipe_pass->set_target(framebuffer_manager->current_post_color(), vid.width, vid.height);
|
||||
}
|
||||
);
|
||||
manager->insert("pp_final_wipe", pp_wipe_pass);
|
||||
manager->insert(
|
||||
"pp_final_wipe_flip",
|
||||
[framebuffer_manager](PassManager&, Rhi&) { framebuffer_manager->swap_post(); }
|
||||
);
|
||||
|
||||
manager->insert(
|
||||
"final_composite_prepare",
|
||||
[final_composite_pass, framebuffer_manager](PassManager&, Rhi&)
|
||||
{
|
||||
final_composite_pass->set_texture(framebuffer_manager->previous_post_color(), vid.width, vid.height);
|
||||
final_composite_pass->set_output(kNullHandle, vid.realwidth, vid.realheight, true, true);
|
||||
}
|
||||
);
|
||||
manager->insert("final_composite", final_composite_pass);
|
||||
|
||||
manager->insert("imgui", imgui_pass);
|
||||
|
||||
manager->insert(
|
||||
"present",
|
||||
[](PassManager&, Rhi& rhi) {},
|
||||
[framebuffer_manager](PassManager&, Rhi& rhi)
|
||||
{
|
||||
rhi.present();
|
||||
rhi.finish();
|
||||
framebuffer_manager->reset_post();
|
||||
|
||||
// TODO fix this: it's an ugly hack to work around issues with wipes
|
||||
// Why this works:
|
||||
// - Menus run F_RunWipe which is an inner update loop calling I_FinishUpdate, with this global set
|
||||
// - After exiting F_RunWipe, g_2d should normally be cleared by I_FinishUpdate
|
||||
// - Unfortunately, the menu has already run all its draw calls when exiting F_RunWipe
|
||||
// - That causes a single-frame flash of no 2d content, which is an epilepsy risk.
|
||||
// - By not clearing the 2d context, we are redrawing 2d every frame of the wipe
|
||||
// - This "works" because we draw 2d to the normal color buffer, not the postprocessed screen.
|
||||
// - It does result in the FPS counter being mangled during the wipe though.
|
||||
// - To fix the issues around wipes, wipes need to be a "sub" game state, and eliminate the inner tic loops.
|
||||
if (!WipeInAction)
|
||||
{
|
||||
g_2d = Twodee();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return manager;
|
||||
}
|
||||
|
||||
void I_FinishUpdate(void)
|
||||
{
|
||||
if (rendermode == render_none)
|
||||
|
|
@ -112,11 +271,9 @@ void I_FinishUpdate(void)
|
|||
io.DisplaySize.y = vid.realheight;
|
||||
ImGui::NewFrame();
|
||||
|
||||
if (rhi_changed())
|
||||
if (rhi_changed() || !g_passmanager)
|
||||
{
|
||||
// reinitialize passes
|
||||
g_sw_pass = SoftwareBlitPass();
|
||||
g_imgui_pass = ImguiPass();
|
||||
g_passmanager = build_pass_manager();
|
||||
}
|
||||
|
||||
rhi::Rhi* rhi = sys::get_rhi(sys::g_current_rhi);
|
||||
|
|
@ -127,48 +284,5 @@ void I_FinishUpdate(void)
|
|||
return;
|
||||
}
|
||||
|
||||
// Prepare phase
|
||||
if (rendermode == render_soft)
|
||||
{
|
||||
g_sw_pass.prepass(*rhi);
|
||||
}
|
||||
g_imgui_pass.prepass(*rhi);
|
||||
|
||||
// Transfer phase
|
||||
Handle<TransferContext> tc;
|
||||
tc = rhi->begin_transfer();
|
||||
|
||||
if (rendermode == render_soft)
|
||||
{
|
||||
g_sw_pass.transfer(*rhi, tc);
|
||||
}
|
||||
g_imgui_pass.transfer(*rhi, tc);
|
||||
|
||||
rhi->end_transfer(tc);
|
||||
|
||||
// Graphics phase
|
||||
Handle<GraphicsContext> gc;
|
||||
gc = rhi->begin_graphics();
|
||||
|
||||
// Standard drawing passes...
|
||||
if (rendermode == render_soft)
|
||||
{
|
||||
g_sw_pass.graphics(*rhi, gc);
|
||||
}
|
||||
g_imgui_pass.graphics(*rhi, gc);
|
||||
|
||||
rhi->end_graphics(gc);
|
||||
|
||||
// Postpass phase
|
||||
if (rendermode == render_soft)
|
||||
{
|
||||
g_sw_pass.postpass(*rhi);
|
||||
}
|
||||
g_imgui_pass.postpass(*rhi);
|
||||
|
||||
// Present
|
||||
|
||||
rhi->present();
|
||||
|
||||
rhi->finish();
|
||||
g_passmanager->render(*rhi);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -517,10 +517,7 @@ void M_Drawer(void)
|
|||
}
|
||||
else if (!WipeInAction && currentMenu != &PAUSE_PlaybackMenuDef)
|
||||
{
|
||||
if (rendermode == render_opengl) // OGL can't handle what SW is doing so let's fake it;
|
||||
V_DrawFadeScreen(122, 3); // palette index aproximation...
|
||||
else // Software can keep its unique fade
|
||||
V_DrawCustomFadeScreen("FADEMAP0", 4); // now that's more readable with a faded background (yeah like Quake...)
|
||||
V_DrawFadeScreen(122, 3);
|
||||
}
|
||||
|
||||
if (currentMenu->drawroutine)
|
||||
|
|
@ -4802,7 +4799,7 @@ static void M_DrawChallengePreview(INT32 x, INT32 y)
|
|||
unlockable_t *ref = NULL;
|
||||
UINT8 *colormap = NULL;
|
||||
UINT16 specialmap = NEXTMAP_INVALID;
|
||||
|
||||
|
||||
if (challengesmenu.currentunlock >= MAXUNLOCKABLES)
|
||||
{
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ void Patch_Free(patch_t *patch)
|
|||
{
|
||||
if (!patch || patch == missingpat)
|
||||
return;
|
||||
|
||||
Patch_FreeData(patch);
|
||||
Z_Free(patch);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,12 @@
|
|||
// 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 "gl3_core_rhi.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
|
@ -13,7 +22,7 @@
|
|||
using namespace srb2;
|
||||
using namespace rhi;
|
||||
|
||||
#if 1
|
||||
#ifndef NDEBUG
|
||||
#define GL_ASSERT \
|
||||
{ \
|
||||
GLenum __err = gl_->GetError(); \
|
||||
|
|
@ -56,6 +65,11 @@ constexpr std::tuple<GLenum, GLenum, GLuint> map_pixel_data_format(rhi::PixelFor
|
|||
type = GL_UNSIGNED_BYTE;
|
||||
size = 1;
|
||||
break;
|
||||
case rhi::PixelFormat::kRG8:
|
||||
layout = GL_RG;
|
||||
type = GL_UNSIGNED_BYTE;
|
||||
size = 2;
|
||||
break;
|
||||
case rhi::PixelFormat::kRGBA8:
|
||||
layout = GL_RGBA;
|
||||
type = GL_UNSIGNED_BYTE;
|
||||
|
|
@ -77,6 +91,27 @@ constexpr GLenum map_texture_format(rhi::TextureFormat format)
|
|||
return GL_RGB;
|
||||
case rhi::TextureFormat::kLuminance:
|
||||
return GL_RED;
|
||||
case rhi::TextureFormat::kLuminanceAlpha:
|
||||
return GL_RG;
|
||||
default:
|
||||
return GL_ZERO;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr GLenum map_internal_texture_format(rhi::TextureFormat format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case rhi::TextureFormat::kRGBA:
|
||||
return GL_RGBA8;
|
||||
case rhi::TextureFormat::kRGB:
|
||||
return GL_RGB8;
|
||||
case rhi::TextureFormat::kLuminance:
|
||||
return GL_R8;
|
||||
case rhi::TextureFormat::kLuminanceAlpha:
|
||||
return GL_RG8;
|
||||
case rhi::TextureFormat::kDepth:
|
||||
return GL_DEPTH_COMPONENT24;
|
||||
default:
|
||||
return GL_ZERO;
|
||||
}
|
||||
|
|
@ -286,6 +321,27 @@ constexpr const char* map_uniform_attribute_symbol_name(rhi::UniformName name)
|
|||
return "u_projection";
|
||||
case rhi::UniformName::kTexCoord0Transform:
|
||||
return "u_texcoord0_transform";
|
||||
case rhi::UniformName::kSampler0IsIndexedAlpha:
|
||||
return "u_sampler0_is_indexed_alpha";
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr const char* map_uniform_enable_define(rhi::UniformName name)
|
||||
{
|
||||
switch (name)
|
||||
{
|
||||
case rhi::UniformName::kTime:
|
||||
return "ENABLE_U_TIME";
|
||||
case rhi::UniformName::kProjection:
|
||||
return "ENABLE_U_PROJECTION";
|
||||
case rhi::UniformName::kModelView:
|
||||
return "ENABLE_U_MODELVIEW";
|
||||
case rhi::UniformName::kTexCoord0Transform:
|
||||
return "ENABLE_U_TEXCOORD0_TRANSFORM";
|
||||
case rhi::UniformName::kSampler0IsIndexedAlpha:
|
||||
return "ENABLE_U_SAMPLER0_IS_INDEXED_ALPHA";
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
|
|
@ -308,6 +364,23 @@ constexpr const char* map_sampler_symbol_name(rhi::SamplerName name)
|
|||
}
|
||||
}
|
||||
|
||||
constexpr const char* map_sampler_enable_define(rhi::SamplerName name)
|
||||
{
|
||||
switch (name)
|
||||
{
|
||||
case rhi::SamplerName::kSampler0:
|
||||
return "ENABLE_S_SAMPLER0";
|
||||
case rhi::SamplerName::kSampler1:
|
||||
return "ENABLE_S_SAMPLER1";
|
||||
case rhi::SamplerName::kSampler2:
|
||||
return "ENABLE_S_SAMPLER2";
|
||||
case rhi::SamplerName::kSampler3:
|
||||
return "ENABLE_S_SAMPLER3";
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
constexpr GLenum map_vertex_attribute_format(rhi::VertexAttributeFormat format)
|
||||
{
|
||||
switch (format)
|
||||
|
|
@ -423,8 +496,13 @@ rhi::Handle<rhi::Texture> GlCoreRhi::create_texture(const rhi::TextureDesc& desc
|
|||
{
|
||||
SRB2_ASSERT(graphics_context_active_ == false);
|
||||
|
||||
GLenum internal_format = map_texture_format(desc.format);
|
||||
GLenum internal_format = map_internal_texture_format(desc.format);
|
||||
SRB2_ASSERT(internal_format != GL_ZERO);
|
||||
GLenum format = GL_RGBA;
|
||||
if (desc.format == TextureFormat::kDepth)
|
||||
{
|
||||
format = GL_DEPTH_COMPONENT;
|
||||
}
|
||||
|
||||
GLuint name = 0;
|
||||
gl_->GenTextures(1, &name);
|
||||
|
|
@ -439,7 +517,7 @@ rhi::Handle<rhi::Texture> GlCoreRhi::create_texture(const rhi::TextureDesc& desc
|
|||
GL_ASSERT
|
||||
gl_->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
|
||||
GL_ASSERT
|
||||
gl_->TexImage2D(GL_TEXTURE_2D, 0, internal_format, desc.width, desc.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
|
||||
gl_->TexImage2D(GL_TEXTURE_2D, 0, internal_format, desc.width, desc.height, 0, format, GL_UNSIGNED_BYTE, nullptr);
|
||||
GL_ASSERT
|
||||
|
||||
GlCoreTexture texture;
|
||||
|
|
@ -478,13 +556,19 @@ void GlCoreRhi::update_texture(
|
|||
SRB2_ASSERT(texture_slab_.is_valid(texture) == true);
|
||||
auto& t = texture_slab_[texture];
|
||||
|
||||
// Each row of pixels must be on the unpack alignment boundary.
|
||||
// This alignment is not user changeable until OpenGL 4.
|
||||
constexpr const int32_t kUnpackAlignment = 4;
|
||||
|
||||
GLenum format = GL_RGBA;
|
||||
GLenum type = GL_UNSIGNED_BYTE;
|
||||
GLuint size = 0;
|
||||
std::tie(format, type, size) = map_pixel_data_format(data_format);
|
||||
SRB2_ASSERT(format != GL_ZERO && type != GL_ZERO);
|
||||
SRB2_ASSERT(map_texture_format(t.desc.format) == format);
|
||||
SRB2_ASSERT(region.w * region.h * size == data.size_bytes());
|
||||
|
||||
int32_t expected_row_span = (((size * region.w) + kUnpackAlignment - 1) / kUnpackAlignment) * kUnpackAlignment;
|
||||
SRB2_ASSERT(expected_row_span * region.h == data.size_bytes());
|
||||
SRB2_ASSERT(region.x + region.w <= t.desc.width && region.y + region.h <= t.desc.height);
|
||||
|
||||
gl_->ActiveTexture(GL_TEXTURE0);
|
||||
|
|
@ -740,14 +824,79 @@ rhi::Handle<rhi::Pipeline> GlCoreRhi::create_pipeline(const PipelineDesc& desc)
|
|||
}
|
||||
}
|
||||
}
|
||||
for (auto& uniform_group : desc.uniform_input.enabled_uniforms)
|
||||
{
|
||||
for (auto& uniform : uniform_group)
|
||||
{
|
||||
for (auto const& req_uni_group : reqs.uniforms.uniform_groups)
|
||||
{
|
||||
for (auto const& req_uni : req_uni_group)
|
||||
{
|
||||
if (req_uni.name == uniform && !req_uni.required)
|
||||
{
|
||||
vert_src_processed.append("#define ");
|
||||
vert_src_processed.append(map_uniform_enable_define(uniform));
|
||||
vert_src_processed.append("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
string_i = new_i + 1;
|
||||
} while (string_i != std::string::npos);
|
||||
|
||||
std::string frag_src_processed;
|
||||
string_i = 0;
|
||||
do
|
||||
{
|
||||
std::string::size_type new_i = frag_src.find('\n', string_i);
|
||||
if (new_i == std::string::npos)
|
||||
{
|
||||
break;
|
||||
}
|
||||
std::string_view line_view(frag_src.c_str() + string_i, new_i - string_i + 1);
|
||||
frag_src_processed.append(line_view);
|
||||
if (line_view.rfind("#version ", 0) == 0)
|
||||
{
|
||||
for (auto& sampler : desc.sampler_input.enabled_samplers)
|
||||
{
|
||||
for (auto const& require_sampler : reqs.samplers.samplers)
|
||||
{
|
||||
if (sampler == require_sampler.name && !require_sampler.required)
|
||||
{
|
||||
frag_src_processed.append("#define ");
|
||||
frag_src_processed.append(map_sampler_enable_define(sampler));
|
||||
frag_src_processed.append("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
for (auto& uniform_group : desc.uniform_input.enabled_uniforms)
|
||||
{
|
||||
for (auto& uniform : uniform_group)
|
||||
{
|
||||
for (auto const& req_uni_group : reqs.uniforms.uniform_groups)
|
||||
{
|
||||
for (auto const& req_uni : req_uni_group)
|
||||
{
|
||||
if (req_uni.name == uniform && !req_uni.required)
|
||||
{
|
||||
frag_src_processed.append("#define ");
|
||||
frag_src_processed.append(map_uniform_enable_define(uniform));
|
||||
frag_src_processed.append("\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
string_i = new_i + 1;
|
||||
} while (string_i != std::string::npos);
|
||||
|
||||
const char* vert_src_arr[1] = {vert_src_processed.c_str()};
|
||||
const GLint vert_src_arr_lens[1] = {static_cast<GLint>(vert_src_processed.size())};
|
||||
const char* frag_src_arr[1] = {frag_src.c_str()};
|
||||
const GLint frag_src_arr_lens[1] = {static_cast<GLint>(frag_src.size())};
|
||||
const char* frag_src_arr[1] = {frag_src_processed.c_str()};
|
||||
const GLint frag_src_arr_lens[1] = {static_cast<GLint>(frag_src_processed.size())};
|
||||
|
||||
vertex = gl_->CreateShader(GL_VERTEX_SHADER);
|
||||
gl_->ShaderSource(vertex, 1, vert_src_arr, vert_src_arr_lens);
|
||||
|
|
@ -1380,6 +1529,8 @@ void GlCoreRhi::bind_index_buffer(Handle<GraphicsContext> ctx, Handle<Buffer> bu
|
|||
|
||||
SRB2_ASSERT(ib.desc.type == rhi::BufferType::kIndexBuffer);
|
||||
|
||||
current_index_buffer_ = buffer;
|
||||
|
||||
gl_->BindBuffer(GL_ELEMENT_ARRAY_BUFFER, ib.buffer);
|
||||
}
|
||||
|
||||
|
|
@ -1412,11 +1563,20 @@ void GlCoreRhi::draw(Handle<GraphicsContext> ctx, uint32_t vertex_count, uint32_
|
|||
void GlCoreRhi::draw_indexed(Handle<GraphicsContext> ctx, uint32_t index_count, uint32_t first_index)
|
||||
{
|
||||
SRB2_ASSERT(graphics_context_active_ == true && graphics_context_generation_ == ctx.generation());
|
||||
|
||||
SRB2_ASSERT(current_index_buffer_ != kNullHandle);
|
||||
#ifndef NDEBUG
|
||||
{
|
||||
auto& ib = buffer_slab_[current_index_buffer_];
|
||||
SRB2_ASSERT((index_count + first_index) * 2 + index_buffer_offset_ <= ib.desc.size);
|
||||
}
|
||||
#endif
|
||||
|
||||
gl_->DrawElements(
|
||||
map_primitive_mode(current_primitive_type_),
|
||||
index_count,
|
||||
GL_UNSIGNED_SHORT,
|
||||
reinterpret_cast<const void*>(first_index * 2 + index_buffer_offset_)
|
||||
(const void*)((size_t)first_index * 2 + index_buffer_offset_)
|
||||
);
|
||||
GL_ASSERT
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,12 @@
|
|||
// 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_RHI_GLES2_RHI_HPP__
|
||||
#define __SRB2_RHI_GLES2_RHI_HPP__
|
||||
|
||||
|
|
@ -145,6 +154,8 @@ class GlCoreRhi final : public Rhi
|
|||
Slab<GlCoreUniformSet> uniform_set_slab_;
|
||||
Slab<GlCoreBindingSet> binding_set_slab_;
|
||||
|
||||
Handle<Buffer> current_index_buffer_;
|
||||
|
||||
std::unordered_map<GlCoreFramebufferKey, uint32_t> framebuffers_ {16};
|
||||
|
||||
struct DefaultRenderPassState
|
||||
|
|
|
|||
|
|
@ -1,3 +1,12 @@
|
|||
// 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 "gles2_rhi.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,12 @@
|
|||
// 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_RHI_GLES2_RHI_HPP__
|
||||
#define __SRB2_RHI_GLES2_RHI_HPP__
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,12 @@
|
|||
// 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_RHI_HANDLE_HPP__
|
||||
#define __SRB2_RHI_HANDLE_HPP__
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,12 @@
|
|||
// 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 "rhi.hpp"
|
||||
|
||||
#include <exception>
|
||||
|
|
@ -14,8 +23,9 @@ const ProgramRequirements srb2::rhi::kProgramRequirementsUnshaded = {
|
|||
ProgramVertexInput {VertexAttributeName::kTexCoord0, VertexAttributeFormat::kFloat2, false},
|
||||
ProgramVertexInput {VertexAttributeName::kColor, VertexAttributeFormat::kFloat4, false}}},
|
||||
ProgramUniformRequirements {
|
||||
{{{UniformName::kProjection}}, {{UniformName::kModelView, UniformName::kTexCoord0Transform}}}},
|
||||
ProgramSamplerRequirements {{ProgramSamplerInput {SamplerName::kSampler0, true}}}};
|
||||
{{{{UniformName::kProjection, true}}},
|
||||
{{{UniformName::kModelView, true}, {UniformName::kTexCoord0Transform, true}}}}},
|
||||
ProgramSamplerRequirements {{{SamplerName::kSampler0, true}}}};
|
||||
|
||||
const ProgramRequirements srb2::rhi::kProgramRequirementsUnshadedPaletted = {
|
||||
ProgramVertexInputRequirements {
|
||||
|
|
@ -23,9 +33,19 @@ const ProgramRequirements srb2::rhi::kProgramRequirementsUnshadedPaletted = {
|
|||
ProgramVertexInput {VertexAttributeName::kTexCoord0, VertexAttributeFormat::kFloat2, false},
|
||||
ProgramVertexInput {VertexAttributeName::kColor, VertexAttributeFormat::kFloat4, false}}},
|
||||
ProgramUniformRequirements {
|
||||
{{{UniformName::kProjection}}, {{UniformName::kModelView, UniformName::kTexCoord0Transform}}}},
|
||||
{{{{UniformName::kProjection, true}}},
|
||||
{{{UniformName::kModelView, true},
|
||||
{UniformName::kTexCoord0Transform, true},
|
||||
{UniformName::kSampler0IsIndexedAlpha, false}}}}},
|
||||
ProgramSamplerRequirements {
|
||||
{ProgramSamplerInput {SamplerName::kSampler0, true}, ProgramSamplerInput {SamplerName::kSampler1, true}}}};
|
||||
{{SamplerName::kSampler0, true}, {SamplerName::kSampler1, true}, {SamplerName::kSampler2, false}}}};
|
||||
|
||||
const ProgramRequirements srb2::rhi::kProgramRequirementsPostprocessWipe = {
|
||||
ProgramVertexInputRequirements {
|
||||
{ProgramVertexInput {VertexAttributeName::kPosition, VertexAttributeFormat::kFloat3, true},
|
||||
ProgramVertexInput {VertexAttributeName::kTexCoord0, VertexAttributeFormat::kFloat2, true}}},
|
||||
ProgramUniformRequirements {{{{{UniformName::kProjection, true}, {UniformName::kModelView, true}}}}},
|
||||
ProgramSamplerRequirements {{{SamplerName::kSampler0, true}, {SamplerName::kSampler1, true}}}};
|
||||
|
||||
const ProgramRequirements& rhi::program_requirements_for_program(PipelineProgram program) noexcept
|
||||
{
|
||||
|
|
@ -35,6 +55,8 @@ const ProgramRequirements& rhi::program_requirements_for_program(PipelineProgram
|
|||
return kProgramRequirementsUnshaded;
|
||||
case PipelineProgram::kUnshadedPaletted:
|
||||
return kProgramRequirementsUnshadedPaletted;
|
||||
case PipelineProgram::kPostprocessWipe:
|
||||
return kProgramRequirementsPostprocessWipe;
|
||||
default:
|
||||
std::terminate();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,12 @@
|
|||
// 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_RHI_RHI_HPP__
|
||||
#define __SRB2_RHI_RHI_HPP__
|
||||
|
||||
|
|
@ -63,6 +72,7 @@ enum class UniformFormat
|
|||
enum class PixelFormat
|
||||
{
|
||||
kR8,
|
||||
kRG8,
|
||||
kRGBA8,
|
||||
kDepth16,
|
||||
kStencil8
|
||||
|
|
@ -71,8 +81,10 @@ enum class PixelFormat
|
|||
enum class TextureFormat
|
||||
{
|
||||
kLuminance,
|
||||
kLuminanceAlpha,
|
||||
kRGB,
|
||||
kRGBA
|
||||
kRGBA,
|
||||
kDepth
|
||||
};
|
||||
|
||||
enum class CompareFunc
|
||||
|
|
@ -152,7 +164,8 @@ enum class AttachmentStoreOp
|
|||
enum class PipelineProgram
|
||||
{
|
||||
kUnshaded,
|
||||
kUnshadedPaletted
|
||||
kUnshadedPaletted,
|
||||
kPostprocessWipe
|
||||
};
|
||||
|
||||
enum class BufferType
|
||||
|
|
@ -181,7 +194,8 @@ enum class UniformName
|
|||
kTime,
|
||||
kModelView,
|
||||
kProjection,
|
||||
kTexCoord0Transform
|
||||
kTexCoord0Transform,
|
||||
kSampler0IsIndexedAlpha
|
||||
};
|
||||
|
||||
enum class SamplerName
|
||||
|
|
@ -237,12 +251,12 @@ struct ProgramVertexInputRequirements
|
|||
|
||||
struct ProgramUniformRequirements
|
||||
{
|
||||
srb2::StaticVec<srb2::StaticVec<UniformName, 16>, 4> uniform_groups;
|
||||
srb2::StaticVec<srb2::StaticVec<ProgramUniformInput, 16>, 4> uniform_groups;
|
||||
};
|
||||
|
||||
struct ProgramSamplerRequirements
|
||||
{
|
||||
std::array<std::optional<ProgramSamplerInput>, kMaxSamplers> samplers;
|
||||
srb2::StaticVec<ProgramSamplerInput, kMaxSamplers> samplers;
|
||||
};
|
||||
|
||||
struct ProgramRequirements
|
||||
|
|
@ -254,6 +268,7 @@ struct ProgramRequirements
|
|||
|
||||
extern const ProgramRequirements kProgramRequirementsUnshaded;
|
||||
extern const ProgramRequirements kProgramRequirementsUnshadedPaletted;
|
||||
extern const ProgramRequirements kProgramRequirementsPostprocessWipe;
|
||||
|
||||
const ProgramRequirements& program_requirements_for_program(PipelineProgram program) noexcept;
|
||||
|
||||
|
|
@ -288,6 +303,8 @@ inline constexpr const UniformFormat uniform_format(UniformName name) noexcept
|
|||
return UniformFormat::kMat4;
|
||||
case UniformName::kTexCoord0Transform:
|
||||
return UniformFormat::kMat3;
|
||||
case UniformName::kSampler0IsIndexedAlpha:
|
||||
return UniformFormat::kInt;
|
||||
default:
|
||||
return UniformFormat::kFloat;
|
||||
}
|
||||
|
|
@ -309,8 +326,8 @@ struct VertexAttributeLayoutDesc
|
|||
|
||||
struct VertexInputDesc
|
||||
{
|
||||
std::vector<VertexBufferLayoutDesc> buffer_layouts;
|
||||
std::vector<VertexAttributeLayoutDesc> attr_layouts;
|
||||
srb2::StaticVec<VertexBufferLayoutDesc, 4> buffer_layouts;
|
||||
srb2::StaticVec<VertexAttributeLayoutDesc, 8> attr_layouts;
|
||||
};
|
||||
|
||||
struct UniformInputDesc
|
||||
|
|
@ -489,6 +506,9 @@ struct GraphicsContext
|
|||
{
|
||||
};
|
||||
|
||||
/// @brief The unpack alignment of a row span when uploading pixels to the device.
|
||||
constexpr const std::size_t kPixelRowUnpackAlignment = 4;
|
||||
|
||||
/// @brief An active handle to a rendering device.
|
||||
struct Rhi
|
||||
{
|
||||
|
|
|
|||
|
|
@ -231,7 +231,11 @@ static void SDLSetMode(INT32 width, INT32 height, SDL_bool fullscreen, SDL_bool
|
|||
{
|
||||
OglSdlSurface(vid.width, vid.height);
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
SDL_GL_SetSwapInterval(cv_vidwait.value ? 1 : 0);
|
||||
}
|
||||
|
||||
SDL_GetWindowSize(window, &width, &height);
|
||||
vid.realwidth = static_cast<uint32_t>(width);
|
||||
|
|
|
|||
|
|
@ -1,3 +1,12 @@
|
|||
// 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 "rhi_gl3_core_platform.hpp"
|
||||
|
||||
#include <SDL.h>
|
||||
|
|
@ -33,6 +42,10 @@ std::tuple<std::string, std::string> SdlGlCorePlatform::find_shader_sources(rhi:
|
|||
vertex_lump_name = "rhi_glcore_vertex_unshadedpaletted";
|
||||
fragment_lump_name = "rhi_glcore_fragment_unshadedpaletted";
|
||||
break;
|
||||
case rhi::PipelineProgram::kPostprocessWipe:
|
||||
vertex_lump_name = "rhi_glcore_vertex_postprocesswipe";
|
||||
fragment_lump_name = "rhi_glcore_fragment_postprocesswipe";
|
||||
break;
|
||||
default:
|
||||
std::terminate();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,12 @@
|
|||
// 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_SDL_RHI_GLES2_PLATFORM_HPP__
|
||||
#define __SRB2_SDL_RHI_GLES2_PLATFORM_HPP__
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,12 @@
|
|||
// 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 "rhi_gles2_platform.hpp"
|
||||
|
||||
#include <SDL.h>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,12 @@
|
|||
// 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_SDL_RHI_GLES2_PLATFORM_HPP__
|
||||
#define __SRB2_SDL_RHI_GLES2_PLATFORM_HPP__
|
||||
|
||||
|
|
|
|||
594
src/v_video.cpp
594
src/v_video.cpp
|
|
@ -41,6 +41,8 @@
|
|||
#include "k_boss.h"
|
||||
#include "i_time.h"
|
||||
|
||||
using namespace srb2;
|
||||
|
||||
// Each screen is [vid.width*vid.height];
|
||||
UINT8 *screens[5];
|
||||
// screens[0] = main display window
|
||||
|
|
@ -97,8 +99,12 @@ RGBA_t *pLocalPalette = NULL;
|
|||
RGBA_t *pMasterPalette = NULL;
|
||||
RGBA_t *pGammaCorrectedPalette = NULL;
|
||||
|
||||
hwr2::Twodee srb2::g_2d;
|
||||
|
||||
static size_t currentPaletteSize;
|
||||
|
||||
static UINT8 softwaretranstohwr[11] = { 0, 25, 51, 76,102,127,153,178,204,229,255};
|
||||
|
||||
/*
|
||||
The following was an extremely helpful resource when developing my Colour Cube LUT.
|
||||
http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter24.html
|
||||
|
|
@ -650,7 +656,7 @@ void V_AdjustXYWithSnap(INT32 *x, INT32 *y, UINT32 options, INT32 dupx, INT32 du
|
|||
}
|
||||
}
|
||||
|
||||
static cliprect_t cliprect;
|
||||
static cliprect_t cliprect = {0};
|
||||
|
||||
const cliprect_t *V_GetClipRect(void)
|
||||
{
|
||||
|
|
@ -771,16 +777,11 @@ static inline UINT8 transmappedpdraw(const UINT8 *dest, const UINT8 *source, fix
|
|||
// Draws a patch scaled to arbitrary size.
|
||||
void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vscale, INT32 scrn, patch_t *patch, const UINT8 *colormap)
|
||||
{
|
||||
UINT8 (*patchdrawfunc)(const UINT8*, const UINT8*, fixed_t);
|
||||
UINT32 alphalevel, blendmode;
|
||||
|
||||
fixed_t col, ofs, colfrac, rowfrac, fdup, vdup;
|
||||
fixed_t vdup;
|
||||
INT32 dupx, dupy;
|
||||
const column_t *column;
|
||||
UINT8 *desttop, *dest, *deststart, *destend;
|
||||
const UINT8 *source, *deststop;
|
||||
fixed_t pwidth; // patch width
|
||||
fixed_t offx = 0; // x offset
|
||||
|
||||
const cliprect_t *clip = V_GetClipRect();
|
||||
|
||||
|
|
@ -796,8 +797,6 @@ void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vsca
|
|||
}
|
||||
#endif
|
||||
|
||||
patchdrawfunc = standardpdraw;
|
||||
|
||||
if ((blendmode = ((scrn & V_BLENDMASK) >> V_BLENDSHIFT)))
|
||||
blendmode++; // realign to constants
|
||||
if ((alphalevel = ((scrn & V_ALPHAMASK) >> V_ALPHASHIFT)))
|
||||
|
|
@ -812,15 +811,6 @@ void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vsca
|
|||
if (alphalevel >= 10) // Still inelegible to render?
|
||||
return;
|
||||
}
|
||||
if ((v_translevel = R_GetBlendTable(blendmode, alphalevel)))
|
||||
patchdrawfunc = translucentpdraw;
|
||||
|
||||
v_colormap = NULL;
|
||||
if (colormap)
|
||||
{
|
||||
v_colormap = colormap;
|
||||
patchdrawfunc = (v_translevel) ? transmappedpdraw : mappedpdraw;
|
||||
}
|
||||
|
||||
dupx = vid.dupx;
|
||||
dupy = vid.dupy;
|
||||
|
|
@ -843,11 +833,9 @@ void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vsca
|
|||
|
||||
// only use one dup, to avoid stretching (har har)
|
||||
dupx = dupy = (dupx < dupy ? dupx : dupy);
|
||||
fdup = vdup = FixedMul(dupx<<FRACBITS, pscale);
|
||||
vdup = FixedMul(dupx<<FRACBITS, pscale);
|
||||
if (vscale != pscale)
|
||||
vdup = FixedMul(dupx<<FRACBITS, vscale);
|
||||
colfrac = FixedDiv(FRACUNIT, fdup);
|
||||
rowfrac = FixedDiv(FRACUNIT, vdup);
|
||||
|
||||
{
|
||||
fixed_t offsetx = 0, offsety = 0;
|
||||
|
|
@ -869,18 +857,10 @@ void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vsca
|
|||
y -= offsety;
|
||||
}
|
||||
|
||||
desttop = screens[scrn&V_SCREENMASK];
|
||||
|
||||
if (!desttop)
|
||||
return;
|
||||
|
||||
deststop = desttop + vid.rowbytes * vid.height;
|
||||
|
||||
if (scrn & V_NOSCALESTART)
|
||||
{
|
||||
x >>= FRACBITS;
|
||||
y >>= FRACBITS;
|
||||
desttop += (y*vid.width) + x;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -894,8 +874,6 @@ void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vsca
|
|||
{
|
||||
V_AdjustXYWithSnap(&x, &y, scrn, dupx, dupy);
|
||||
}
|
||||
|
||||
desttop += (y*vid.width) + x;
|
||||
}
|
||||
|
||||
if (pscale != FRACUNIT) // scale width properly
|
||||
|
|
@ -908,104 +886,73 @@ void V_DrawStretchyFixedPatch(fixed_t x, fixed_t y, fixed_t pscale, fixed_t vsca
|
|||
else
|
||||
pwidth = patch->width * dupx;
|
||||
|
||||
deststart = desttop;
|
||||
destend = desttop + pwidth;
|
||||
float fdupy = FIXED_TO_FLOAT(vdup);
|
||||
|
||||
for (col = 0; (col>>FRACBITS) < patch->width; col += colfrac, ++offx, desttop++)
|
||||
float fx = x;
|
||||
float fy = y;
|
||||
float fx2 = fx + pwidth;
|
||||
float fy2 = fy + static_cast<float>(patch->height) * fdupy;
|
||||
float falpha = 1.f;
|
||||
float umin = 0.f;
|
||||
float umax = 1.f;
|
||||
float vmin = 0.f;
|
||||
float vmax = 1.f;
|
||||
|
||||
// flip UVs
|
||||
if (scrn & V_FLIP)
|
||||
{
|
||||
INT32 topdelta, prevdelta = -1;
|
||||
|
||||
if (scrn & V_FLIP) // offx is measured from right edge instead of left
|
||||
{
|
||||
if (x+pwidth-offx < (clip ? clip->left : 0)) // don't draw off the left of the screen (WRAP PREVENTION)
|
||||
break;
|
||||
if (x+pwidth-offx >= (clip ? clip->right : vid.width)) // don't draw off the right of the screen (WRAP PREVENTION)
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (x+offx < (clip ? clip->left : 0)) // don't draw off the left of the screen (WRAP PREVENTION)
|
||||
continue;
|
||||
if (x+offx >= (clip ? clip->right : vid.width)) // don't draw off the right of the screen (WRAP PREVENTION)
|
||||
break;
|
||||
}
|
||||
|
||||
column = (const column_t *)((const UINT8 *)(patch->columns) + (patch->columnofs[col>>FRACBITS]));
|
||||
|
||||
while (column->topdelta != 0xff)
|
||||
{
|
||||
fixed_t offy = 0;
|
||||
|
||||
topdelta = column->topdelta;
|
||||
if (topdelta <= prevdelta)
|
||||
topdelta += prevdelta;
|
||||
prevdelta = topdelta;
|
||||
source = (const UINT8 *)(column) + 3;
|
||||
|
||||
dest = desttop;
|
||||
if (scrn & V_FLIP)
|
||||
dest = deststart + (destend - dest);
|
||||
topdelta = FixedInt(FixedMul(topdelta << FRACBITS, vdup));
|
||||
dest += topdelta * vid.width;
|
||||
|
||||
if (scrn & V_VFLIP)
|
||||
{
|
||||
for (ofs = (column->length << FRACBITS)-1; dest < deststop && ofs >= 0; ofs -= rowfrac, ++offy)
|
||||
{
|
||||
if (clip != NULL)
|
||||
{
|
||||
const INT32 cy = y + topdelta - offy;
|
||||
|
||||
if (cy < clip->top) // don't draw off the top of the clip rect
|
||||
{
|
||||
dest += vid.width;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cy >= clip->bottom) // don't draw off the bottom of the clip rect
|
||||
{
|
||||
dest += vid.width;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (dest >= screens[scrn&V_SCREENMASK]) // don't draw off the top of the screen (CRASH PREVENTION)
|
||||
*dest = patchdrawfunc(dest, source, ofs);
|
||||
|
||||
dest += vid.width;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (ofs = 0; dest < deststop && ofs < (column->length << FRACBITS); ofs += rowfrac, ++offy)
|
||||
{
|
||||
if (clip != NULL)
|
||||
{
|
||||
const INT32 cy = y + topdelta + offy;
|
||||
|
||||
if (cy < clip->top) // don't draw off the top of the clip rect
|
||||
{
|
||||
dest += vid.width;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cy >= clip->bottom) // don't draw off the bottom of the clip rect
|
||||
{
|
||||
dest += vid.width;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (dest >= screens[scrn&V_SCREENMASK]) // don't draw off the top of the screen (CRASH PREVENTION)
|
||||
*dest = patchdrawfunc(dest, source, ofs);
|
||||
|
||||
dest += vid.width;
|
||||
}
|
||||
}
|
||||
|
||||
column = (const column_t *)((const UINT8 *)column + column->length + 4);
|
||||
}
|
||||
umin = 1.f - umin;
|
||||
umax = 1.f - umax;
|
||||
}
|
||||
if (scrn & V_VFLIP)
|
||||
{
|
||||
vmin = 1.f - vmin;
|
||||
vmax = 1.f - vmax;
|
||||
}
|
||||
|
||||
if (alphalevel > 0 && alphalevel <= 10)
|
||||
{
|
||||
falpha = (10 - alphalevel) / 10.f;
|
||||
}
|
||||
hwr2::Draw2dBlend blend = hwr2::Draw2dBlend::kModulate;
|
||||
switch (blendmode)
|
||||
{
|
||||
case AST_MODULATE:
|
||||
blend = hwr2::Draw2dBlend::kModulate;
|
||||
break;
|
||||
case AST_ADD:
|
||||
blend = hwr2::Draw2dBlend::kAdditive;
|
||||
break;
|
||||
|
||||
// Note: SRB2 has these blend modes flipped compared to GL and Vulkan.
|
||||
// SRB2's Subtract is Dst - Src. OpenGL is Src - Dst. And vice versa for reverse.
|
||||
// Twodee will use the GL definitions.
|
||||
case AST_SUBTRACT:
|
||||
blend = hwr2::Draw2dBlend::kReverseSubtractive;
|
||||
break;
|
||||
case AST_REVERSESUBTRACT:
|
||||
blend = hwr2::Draw2dBlend::kSubtractive;
|
||||
break;
|
||||
default:
|
||||
blend = hwr2::Draw2dBlend::kModulate;
|
||||
break;
|
||||
}
|
||||
|
||||
auto builder = g_2d.begin_quad();
|
||||
builder
|
||||
.patch(patch)
|
||||
.rect(fx, fy, fx2 - fx, fy2 - fy)
|
||||
.flip((scrn & V_FLIP) > 0)
|
||||
.vflip((scrn & V_VFLIP) > 0)
|
||||
.color(1, 1, 1, falpha)
|
||||
.blend(blend)
|
||||
.colormap(colormap);
|
||||
|
||||
if (clip && clip->enabled)
|
||||
{
|
||||
builder.clip(clip->left, clip->top, clip->right, clip->bottom);
|
||||
}
|
||||
builder.done();
|
||||
}
|
||||
|
||||
// Draws a patch cropped and scaled to arbitrary size.
|
||||
|
|
@ -1067,9 +1014,6 @@ void V_DrawBlock(INT32 x, INT32 y, INT32 scrn, INT32 width, INT32 height, const
|
|||
//
|
||||
void V_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c)
|
||||
{
|
||||
UINT8 *dest;
|
||||
const UINT8 *deststop;
|
||||
|
||||
if (rendermode == render_none)
|
||||
return;
|
||||
|
||||
|
|
@ -1122,13 +1066,18 @@ void V_DrawFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c)
|
|||
if (y + h > vid.height)
|
||||
h = vid.height - y;
|
||||
|
||||
dest = screens[0] + y*vid.width + x;
|
||||
deststop = screens[0] + vid.rowbytes * vid.height;
|
||||
|
||||
c &= 255;
|
||||
|
||||
for (;(--h >= 0) && dest < deststop; dest += vid.width)
|
||||
memset(dest, c, w * vid.bpp);
|
||||
RGBA_t color = pMasterPalette[c];
|
||||
UINT8 r = (color.rgba & 0xFF);
|
||||
UINT8 g = (color.rgba & 0xFF00) >> 8;
|
||||
UINT8 b = (color.rgba & 0xFF0000) >> 16;
|
||||
|
||||
g_2d.begin_quad()
|
||||
.patch(nullptr)
|
||||
.color(r / 255.f, g / 255.f, b / 255.f, 1.f)
|
||||
.rect(x, y, w, h)
|
||||
.done();
|
||||
}
|
||||
|
||||
#ifdef HWRENDER
|
||||
|
|
@ -1169,10 +1118,6 @@ static UINT32 V_GetHWConsBackColor(void)
|
|||
|
||||
void V_DrawFillConsoleMap(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c)
|
||||
{
|
||||
UINT8 *dest;
|
||||
const UINT8 *deststop;
|
||||
INT32 u;
|
||||
UINT8 *fadetable;
|
||||
UINT32 alphalevel = 0;
|
||||
|
||||
if (rendermode == render_none)
|
||||
|
|
@ -1231,37 +1176,18 @@ void V_DrawFillConsoleMap(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c)
|
|||
if (y + h > vid.height)
|
||||
h = vid.height-y;
|
||||
|
||||
dest = screens[0] + y*vid.width + x;
|
||||
deststop = screens[0] + vid.rowbytes * vid.height;
|
||||
|
||||
c &= 255;
|
||||
|
||||
// Jimita (12-04-2018)
|
||||
if (alphalevel)
|
||||
{
|
||||
fadetable = R_GetTranslucencyTable(alphalevel) + (c*256);
|
||||
for (;(--h >= 0) && dest < deststop; dest += vid.width)
|
||||
{
|
||||
u = 0;
|
||||
while (u < w)
|
||||
{
|
||||
dest[u] = fadetable[consolebgmap[dest[u]]];
|
||||
u++;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (;(--h >= 0) && dest < deststop; dest += vid.width)
|
||||
{
|
||||
u = 0;
|
||||
while (u < w)
|
||||
{
|
||||
dest[u] = consolebgmap[dest[u]];
|
||||
u++;
|
||||
}
|
||||
}
|
||||
}
|
||||
UINT32 hwcolor = V_GetHWConsBackColor();
|
||||
float r = ((hwcolor & 0xFF000000) >> 24) / 255.f;
|
||||
float g = ((hwcolor & 0xFF0000) >> 16) / 255.f;
|
||||
float b = ((hwcolor & 0xFF00) >> 8) / 255.f;
|
||||
float a = 0.5f; // alphalevel is unused in GL??
|
||||
g_2d.begin_quad()
|
||||
.rect(x, y, w, h)
|
||||
.blend(hwr2::Draw2dBlend::kModulate)
|
||||
.color(r, g, b, a)
|
||||
.done();
|
||||
}
|
||||
|
||||
//
|
||||
|
|
@ -1273,9 +1199,7 @@ void V_DrawFillConsoleMap(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c)
|
|||
//
|
||||
void V_DrawDiag(INT32 x, INT32 y, INT32 wh, INT32 c)
|
||||
{
|
||||
UINT8 *dest;
|
||||
const UINT8 *deststop;
|
||||
INT32 w, h, wait = 0;
|
||||
INT32 w, h;
|
||||
|
||||
if (rendermode == render_none)
|
||||
return;
|
||||
|
|
@ -1321,7 +1245,6 @@ void V_DrawDiag(INT32 x, INT32 y, INT32 wh, INT32 c)
|
|||
return; // zero width/height wouldn't draw anything
|
||||
if (x + w > vid.width)
|
||||
{
|
||||
wait = w - (vid.width - x);
|
||||
w = vid.width - x;
|
||||
}
|
||||
if (y + w > vid.height)
|
||||
|
|
@ -1330,18 +1253,23 @@ void V_DrawDiag(INT32 x, INT32 y, INT32 wh, INT32 c)
|
|||
if (h > w)
|
||||
h = w;
|
||||
|
||||
dest = screens[0] + y*vid.width + x;
|
||||
deststop = screens[0] + vid.rowbytes * vid.height;
|
||||
|
||||
c &= 255;
|
||||
|
||||
for (;(--h >= 0) && dest < deststop; dest += vid.width)
|
||||
{
|
||||
memset(dest, c, w * vid.bpp);
|
||||
if (wait)
|
||||
wait--;
|
||||
else
|
||||
w--;
|
||||
auto builder = g_2d.begin_verts();
|
||||
|
||||
const RGBA_t color = pMasterPalette[c];
|
||||
const float r = ((color.rgba & 0xFF000000) >> 24) / 255.f;
|
||||
const float g = ((color.rgba & 0xFF0000) >> 16) / 255.f;
|
||||
const float b = ((color.rgba & 0xFF00) >> 8) / 255.f;
|
||||
const float a = 1.f;
|
||||
builder.color(r, g, b, a);
|
||||
|
||||
builder
|
||||
.vert(x, y)
|
||||
.vert(x + wh, y + wh)
|
||||
.vert(x, y + wh)
|
||||
.done();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1355,11 +1283,6 @@ void V_DrawDiag(INT32 x, INT32 y, INT32 wh, INT32 c)
|
|||
//
|
||||
void V_DrawFadeFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c, UINT16 color, UINT8 strength)
|
||||
{
|
||||
UINT8 *dest;
|
||||
const UINT8 *deststop;
|
||||
INT32 u;
|
||||
UINT8 *fadetable;
|
||||
|
||||
if (rendermode == render_none)
|
||||
return;
|
||||
|
||||
|
|
@ -1403,23 +1326,42 @@ void V_DrawFadeFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c, UINT16 color, U
|
|||
if (y + h > vid.height)
|
||||
h = vid.height-y;
|
||||
|
||||
dest = screens[0] + y*vid.width + x;
|
||||
deststop = screens[0] + vid.rowbytes * vid.height;
|
||||
float r;
|
||||
float g;
|
||||
float b;
|
||||
float a;
|
||||
hwr2::Draw2dBlend blendmode;
|
||||
|
||||
c &= 255;
|
||||
|
||||
fadetable = ((color & 0xFF00) // Color is not palette index?
|
||||
? ((UINT8 *)colormaps + strength*256) // Do COLORMAP fade.
|
||||
: ((UINT8 *)R_GetTranslucencyTable((9-strength)+1) + color*256)); // Else, do TRANSMAP** fade.
|
||||
for (;(--h >= 0) && dest < deststop; dest += vid.width)
|
||||
if (color & 0xFF00)
|
||||
{
|
||||
u = 0;
|
||||
while (u < w)
|
||||
{
|
||||
dest[u] = fadetable[dest[u]];
|
||||
u++;
|
||||
}
|
||||
// Historical COLORMAP fade
|
||||
// In Ring Racers this is a Mega Drive style per-channel fade (though it'd probably be cool in SRB2 too)
|
||||
// HWR2 will implement as a rev-subtractive rect because colormaps aren't possible in hardware
|
||||
float fstrength = std::clamp(strength / 31.f, 0.f, 1.f);
|
||||
r = std::clamp((fstrength - (0.f / 3.f)) * 3.f, 0.f, 1.f);
|
||||
g = std::clamp((fstrength - (1.f / 3.f)) * 3.f, 0.f, 1.f);
|
||||
b = std::clamp((fstrength - (2.f / 3.f)) * 3.f, 0.f, 1.f);
|
||||
a = 1;
|
||||
|
||||
blendmode = hwr2::Draw2dBlend::kReverseSubtractive;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Historically TRANSMAP fade
|
||||
// This is done by modulative (transparent) blend to the given palette color.
|
||||
byteColor_t bc = V_GetColor(color).s;
|
||||
r = bc.red / 255.f;
|
||||
g = bc.green / 255.f;
|
||||
b = bc.blue / 255.f;
|
||||
a = softwaretranstohwr[std::clamp(static_cast<int>(strength), 0, 10)] / 255.f;
|
||||
blendmode = hwr2::Draw2dBlend::kModulate;
|
||||
}
|
||||
|
||||
g_2d.begin_quad()
|
||||
.blend(blendmode)
|
||||
.color(r, g, b, a)
|
||||
.rect(x, y, w, h)
|
||||
.done();
|
||||
}
|
||||
|
||||
//
|
||||
|
|
@ -1427,11 +1369,10 @@ void V_DrawFadeFill(INT32 x, INT32 y, INT32 w, INT32 h, INT32 c, UINT16 color, U
|
|||
//
|
||||
void V_DrawFlatFill(INT32 x, INT32 y, INT32 w, INT32 h, lumpnum_t flatnum)
|
||||
{
|
||||
INT32 u, v, dupx, dupy;
|
||||
fixed_t dx, dy, xfrac, yfrac;
|
||||
const UINT8 *src, *deststop;
|
||||
UINT8 *flat, *dest;
|
||||
size_t size, lflatsize, flatshift;
|
||||
INT32 dupx;
|
||||
INT32 dupy;
|
||||
size_t size;
|
||||
size_t lflatsize;
|
||||
|
||||
#ifdef HWRENDER
|
||||
if (rendermode == render_opengl)
|
||||
|
|
@ -1440,89 +1381,52 @@ void V_DrawFlatFill(INT32 x, INT32 y, INT32 w, INT32 h, lumpnum_t flatnum)
|
|||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
size = W_LumpLength(flatnum);
|
||||
|
||||
switch (size)
|
||||
{
|
||||
case 4194304: // 2048x2048 lump
|
||||
lflatsize = 2048;
|
||||
flatshift = 11;
|
||||
break;
|
||||
case 1048576: // 1024x1024 lump
|
||||
lflatsize = 1024;
|
||||
flatshift = 10;
|
||||
break;
|
||||
case 262144:// 512x512 lump
|
||||
lflatsize = 512;
|
||||
flatshift = 9;
|
||||
break;
|
||||
case 65536: // 256x256 lump
|
||||
lflatsize = 256;
|
||||
flatshift = 8;
|
||||
break;
|
||||
case 16384: // 128x128 lump
|
||||
lflatsize = 128;
|
||||
flatshift = 7;
|
||||
break;
|
||||
case 1024: // 32x32 lump
|
||||
lflatsize = 32;
|
||||
flatshift = 5;
|
||||
break;
|
||||
case 256: // 16x16 lump
|
||||
lflatsize = 16;
|
||||
flatshift = 4;
|
||||
break;
|
||||
case 64: // 8x8 lump
|
||||
lflatsize = 8;
|
||||
flatshift = 3;
|
||||
break;
|
||||
default: // 64x64 lump
|
||||
lflatsize = 64;
|
||||
flatshift = 6;
|
||||
break;
|
||||
}
|
||||
|
||||
flat = static_cast<UINT8*>(W_CacheLumpNum(flatnum, PU_CACHE));
|
||||
float fsize = lflatsize;
|
||||
|
||||
dupx = dupy = (vid.dupx < vid.dupy ? vid.dupx : vid.dupy);
|
||||
|
||||
dest = screens[0] + y*dupy*vid.width + x*dupx;
|
||||
deststop = screens[0] + vid.rowbytes * vid.height;
|
||||
|
||||
// from V_DrawScaledPatch
|
||||
if (vid.width != BASEVIDWIDTH * dupx)
|
||||
{
|
||||
// dupx adjustments pretend that screen width is BASEVIDWIDTH * dupx,
|
||||
// so center this imaginary screen
|
||||
dest += (vid.width - (BASEVIDWIDTH * dupx)) / 2;
|
||||
}
|
||||
if (vid.height != BASEVIDHEIGHT * dupy)
|
||||
{
|
||||
// same thing here
|
||||
dest += (vid.height - (BASEVIDHEIGHT * dupy)) * vid.width / 2;
|
||||
}
|
||||
|
||||
w *= dupx;
|
||||
h *= dupy;
|
||||
|
||||
dx = FixedDiv(FRACUNIT, dupx<<(FRACBITS-2));
|
||||
dy = FixedDiv(FRACUNIT, dupy<<(FRACBITS-2));
|
||||
|
||||
yfrac = 0;
|
||||
for (v = 0; v < h; v++, dest += vid.width)
|
||||
{
|
||||
xfrac = 0;
|
||||
src = flat + (((yfrac>>FRACBITS) & (lflatsize - 1)) << flatshift);
|
||||
for (u = 0; u < w; u++)
|
||||
{
|
||||
if (&dest[u] > deststop)
|
||||
return;
|
||||
dest[u] = src[(xfrac>>FRACBITS)&(lflatsize-1)];
|
||||
xfrac += dx;
|
||||
}
|
||||
yfrac += dy;
|
||||
}
|
||||
g_2d.begin_verts()
|
||||
.flat(flatnum)
|
||||
.vert(x * dupx, y * dupy, 0, 0)
|
||||
.vert(x * dupx + w * dupx, y * dupy, w / fsize, 0)
|
||||
.vert(x * dupx + w * dupx, y * dupy + h * dupy, w / fsize, h / fsize)
|
||||
.vert(x * dupx, y * dupy, 0, 0)
|
||||
.vert(x * dupx + w * dupx, y * dupy + h * dupy, w / fsize, h / fsize)
|
||||
.vert(x * dupx, y * dupy + h * dupy, 0, h / fsize)
|
||||
.done();
|
||||
}
|
||||
|
||||
//
|
||||
|
|
@ -1619,21 +1523,42 @@ void V_DrawFadeScreen(UINT16 color, UINT8 strength)
|
|||
}
|
||||
#endif
|
||||
|
||||
{
|
||||
const UINT8 *fadetable =
|
||||
(color > 0xFFF0) // Grab a specific colormap palette?
|
||||
? R_GetTranslationColormap(color | 0xFFFF0000, static_cast<skincolornum_t>(strength), GTC_CACHE)
|
||||
: ((color & 0xFF00) // Color is not palette index?
|
||||
? ((UINT8 *)colormaps + strength*256) // Do COLORMAP fade.
|
||||
: ((UINT8 *)R_GetTranslucencyTable((9-strength)+1) + color*256)); // Else, do TRANSMAP** fade.
|
||||
const UINT8 *deststop = screens[0] + vid.rowbytes * vid.height;
|
||||
UINT8 *buf = screens[0];
|
||||
float r;
|
||||
float g;
|
||||
float b;
|
||||
float a;
|
||||
hwr2::Draw2dBlend blendmode;
|
||||
|
||||
// heavily simplified -- we don't need to know x or y
|
||||
// position when we're doing a full screen fade
|
||||
for (; buf < deststop; ++buf)
|
||||
*buf = fadetable[*buf];
|
||||
if (color & 0xFF00)
|
||||
{
|
||||
// Historical COLORMAP fade
|
||||
// In Ring Racers this is a Mega Drive style per-channel fade (though it'd probably be cool in SRB2 too)
|
||||
// HWR2 will implement as a rev-subtractive rect because colormaps aren't possible in hardware
|
||||
float fstrength = std::clamp(strength / 31.f, 0.f, 1.f);
|
||||
r = std::clamp((fstrength - (0.f / 3.f)) * 3.f, 0.f, 1.f);
|
||||
g = std::clamp((fstrength - (1.f / 3.f)) * 3.f, 0.f, 1.f);
|
||||
b = std::clamp((fstrength - (2.f / 3.f)) * 3.f, 0.f, 1.f);
|
||||
a = 1;
|
||||
|
||||
blendmode = hwr2::Draw2dBlend::kReverseSubtractive;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Historically TRANSMAP fade
|
||||
// This is done by modulative (transparent) blend to the given palette color.
|
||||
byteColor_t bc = V_GetColor(color).s;
|
||||
r = bc.red / 255.f;
|
||||
g = bc.green / 255.f;
|
||||
b = bc.blue / 255.f;
|
||||
a = softwaretranstohwr[std::clamp(static_cast<int>(strength), 0, 10)] / 255.f;
|
||||
blendmode = hwr2::Draw2dBlend::kModulate;
|
||||
}
|
||||
|
||||
g_2d.begin_quad()
|
||||
.blend(blendmode)
|
||||
.color(r, g, b, a)
|
||||
.rect(0, 0, vid.width, vid.height)
|
||||
.done();
|
||||
}
|
||||
|
||||
//
|
||||
|
|
@ -1643,6 +1568,8 @@ void V_DrawFadeScreen(UINT16 color, UINT8 strength)
|
|||
//
|
||||
void V_DrawCustomFadeScreen(const char *lump, UINT8 strength)
|
||||
{
|
||||
(void)lump;
|
||||
(void)strength;
|
||||
#ifdef HWRENDER
|
||||
if (rendermode != render_soft && rendermode != render_none)
|
||||
{
|
||||
|
|
@ -1651,57 +1578,30 @@ void V_DrawCustomFadeScreen(const char *lump, UINT8 strength)
|
|||
}
|
||||
#endif
|
||||
|
||||
{
|
||||
lumpnum_t lumpnum = LUMPERROR;
|
||||
lighttable_t *clm = NULL;
|
||||
|
||||
if (lump != NULL)
|
||||
lumpnum = W_GetNumForName(lump);
|
||||
else
|
||||
return;
|
||||
|
||||
if (lumpnum != LUMPERROR)
|
||||
{
|
||||
clm = static_cast<lighttable_t*>(Z_MallocAlign(COLORMAP_SIZE, PU_STATIC, NULL, 8));
|
||||
W_ReadLump(lumpnum, clm);
|
||||
|
||||
if (clm != NULL)
|
||||
{
|
||||
const UINT8 *fadetable = ((UINT8 *)clm + strength*256);
|
||||
const UINT8 *deststop = screens[0] + vid.rowbytes * vid.height;
|
||||
UINT8 *buf = screens[0];
|
||||
|
||||
// heavily simplified -- we don't need to know x or y
|
||||
// position when we're doing a full screen fade
|
||||
for (; buf < deststop; ++buf)
|
||||
*buf = fadetable[*buf];
|
||||
|
||||
Z_Free(clm);
|
||||
clm = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
// NOTE: This is not implementable in HWR2.
|
||||
}
|
||||
|
||||
// Simple translucency with one color, over a set number of lines starting from the top.
|
||||
void V_DrawFadeConsBack(INT32 plines)
|
||||
{
|
||||
UINT8 *deststop, *buf;
|
||||
|
||||
UINT32 hwcolor = V_GetHWConsBackColor();
|
||||
#ifdef HWRENDER // not win32 only 19990829 by Kin
|
||||
if (rendermode == render_opengl)
|
||||
{
|
||||
UINT32 hwcolor = V_GetHWConsBackColor();
|
||||
HWR_DrawConsoleBack(hwcolor, plines);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
// heavily simplified -- we don't need to know x or y position,
|
||||
// just the stop position
|
||||
deststop = screens[0] + vid.rowbytes * std::min(plines, vid.height);
|
||||
for (buf = screens[0]; buf < deststop; ++buf)
|
||||
*buf = consolebgmap[*buf];
|
||||
float r = ((hwcolor & 0xFF000000) >> 24) / 255.f;
|
||||
float g = ((hwcolor & 0xFF0000) >> 16) / 255.f;
|
||||
float b = ((hwcolor & 0xFF00) >> 8) / 255.f;
|
||||
float a = 0.5f;
|
||||
g_2d.begin_quad()
|
||||
.rect(0, 0, vid.width, plines)
|
||||
.blend(hwr2::Draw2dBlend::kModulate)
|
||||
.color(r, g, b, a)
|
||||
.done();
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1718,26 +1618,16 @@ void V_EncoreInvertScreen(void)
|
|||
}
|
||||
#endif
|
||||
|
||||
{
|
||||
const UINT8 *deststop = screens[0] + vid.rowbytes * vid.height;
|
||||
UINT8 *buf = screens[0];
|
||||
|
||||
for (; buf < deststop; ++buf)
|
||||
{
|
||||
*buf = NearestColor(
|
||||
255 - pLocalPalette[*buf].s.red,
|
||||
255 - pLocalPalette[*buf].s.green,
|
||||
255 - pLocalPalette[*buf].s.blue
|
||||
);
|
||||
}
|
||||
}
|
||||
g_2d.begin_quad()
|
||||
.blend(hwr2::Draw2dBlend::kInvertDest)
|
||||
.color(1, 1, 1, 1)
|
||||
.rect(0, 0, vid.width, vid.height)
|
||||
.done();
|
||||
}
|
||||
|
||||
// Very similar to F_DrawFadeConsBack, except we draw from the middle(-ish) of the screen to the bottom.
|
||||
void V_DrawPromptBack(INT32 boxheight, INT32 color)
|
||||
{
|
||||
UINT8 *deststop, *buf;
|
||||
|
||||
if (color >= 256 && color < 512)
|
||||
{
|
||||
if (boxheight < 0)
|
||||
|
|
@ -1753,50 +1643,50 @@ void V_DrawPromptBack(INT32 boxheight, INT32 color)
|
|||
if (color == INT32_MAX)
|
||||
color = cons_backcolor.value;
|
||||
|
||||
UINT32 hwcolor;
|
||||
switch (color)
|
||||
{
|
||||
case 0: hwcolor = 0xffffff00; break; // White
|
||||
case 1: hwcolor = 0x00000000; break; // Black // Note this is different from V_DrawFadeConsBack
|
||||
case 2: hwcolor = 0xdeb88700; break; // Sepia
|
||||
case 3: hwcolor = 0x40201000; break; // Brown
|
||||
case 4: hwcolor = 0xfa807200; break; // Pink
|
||||
case 5: hwcolor = 0xff69b400; break; // Raspberry
|
||||
case 6: hwcolor = 0xff000000; break; // Red
|
||||
case 7: hwcolor = 0xffd68300; break; // Creamsicle
|
||||
case 8: hwcolor = 0xff800000; break; // Orange
|
||||
case 9: hwcolor = 0xdaa52000; break; // Gold
|
||||
case 10: hwcolor = 0x80800000; break; // Yellow
|
||||
case 11: hwcolor = 0x00ff0000; break; // Emerald
|
||||
case 12: hwcolor = 0x00800000; break; // Green
|
||||
case 13: hwcolor = 0x4080ff00; break; // Cyan
|
||||
case 14: hwcolor = 0x4682b400; break; // Steel
|
||||
case 15: hwcolor = 0x1e90ff00; break; // Periwinkle
|
||||
case 16: hwcolor = 0x0000ff00; break; // Blue
|
||||
case 17: hwcolor = 0xff00ff00; break; // Purple
|
||||
case 18: hwcolor = 0xee82ee00; break; // Lavender
|
||||
// Default green
|
||||
default: hwcolor = 0x00800000; break;
|
||||
}
|
||||
|
||||
#ifdef HWRENDER
|
||||
if (rendermode == render_opengl)
|
||||
{
|
||||
UINT32 hwcolor;
|
||||
switch (color)
|
||||
{
|
||||
case 0: hwcolor = 0xffffff00; break; // White
|
||||
case 1: hwcolor = 0x00000000; break; // Black // Note this is different from V_DrawFadeConsBack
|
||||
case 2: hwcolor = 0xdeb88700; break; // Sepia
|
||||
case 3: hwcolor = 0x40201000; break; // Brown
|
||||
case 4: hwcolor = 0xfa807200; break; // Pink
|
||||
case 5: hwcolor = 0xff69b400; break; // Raspberry
|
||||
case 6: hwcolor = 0xff000000; break; // Red
|
||||
case 7: hwcolor = 0xffd68300; break; // Creamsicle
|
||||
case 8: hwcolor = 0xff800000; break; // Orange
|
||||
case 9: hwcolor = 0xdaa52000; break; // Gold
|
||||
case 10: hwcolor = 0x80800000; break; // Yellow
|
||||
case 11: hwcolor = 0x00ff0000; break; // Emerald
|
||||
case 12: hwcolor = 0x00800000; break; // Green
|
||||
case 13: hwcolor = 0x4080ff00; break; // Cyan
|
||||
case 14: hwcolor = 0x4682b400; break; // Steel
|
||||
case 15: hwcolor = 0x1e90ff00; break; // Periwinkle
|
||||
case 16: hwcolor = 0x0000ff00; break; // Blue
|
||||
case 17: hwcolor = 0xff00ff00; break; // Purple
|
||||
case 18: hwcolor = 0xee82ee00; break; // Lavender
|
||||
// Default green
|
||||
default: hwcolor = 0x00800000; break;
|
||||
}
|
||||
HWR_DrawTutorialBack(hwcolor, boxheight);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
CON_SetupBackColormapEx(color, true);
|
||||
float r = ((color & 0xFF000000) >> 24) / 255.f;
|
||||
float g = ((color & 0xFF0000) >> 16) / 255.f;
|
||||
float b = ((color & 0xFF00) >> 8) / 255.f;
|
||||
float a = (color == 0 ? 0xC0 : 0x80) / 255.f; // make black darker, like software
|
||||
|
||||
// heavily simplified -- we don't need to know x or y position,
|
||||
// just the start and stop positions
|
||||
buf = deststop = screens[0] + vid.rowbytes * vid.height;
|
||||
if (boxheight < 0)
|
||||
buf += vid.rowbytes * boxheight;
|
||||
else // 4 lines of space plus gaps between and some leeway
|
||||
buf -= vid.rowbytes * ((boxheight * 4) + (boxheight/2)*5);
|
||||
for (; buf < deststop; ++buf)
|
||||
*buf = promptbgmap[*buf];
|
||||
INT32 real_boxheight = (boxheight * 4) + (boxheight / 2) * 5;
|
||||
g_2d.begin_quad()
|
||||
.rect(0, vid.height - real_boxheight, vid.width, real_boxheight)
|
||||
.color(r, g, b, a)
|
||||
.done();
|
||||
}
|
||||
|
||||
// Gets string colormap, used for 0x80 color codes
|
||||
|
|
|
|||
|
|
@ -22,6 +22,14 @@
|
|||
#include "hu_stuff.h" // fonts
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
#include "hwr2/twodee.hpp"
|
||||
|
||||
namespace srb2
|
||||
{
|
||||
extern hwr2::Twodee g_2d;
|
||||
} // namespace srb2
|
||||
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
|
|
|
|||
1
thirdparty/CMakeLists.txt
vendored
1
thirdparty/CMakeLists.txt
vendored
|
|
@ -31,4 +31,5 @@ include("cpm-libyuv.cmake")
|
|||
|
||||
add_subdirectory(tcbrindle_span)
|
||||
add_subdirectory(stb_vorbis)
|
||||
add_subdirectory(stb_rect_pack)
|
||||
add_subdirectory(glad)
|
||||
|
|
|
|||
1
thirdparty/cpm-imgui.cmake
vendored
1
thirdparty/cpm-imgui.cmake
vendored
|
|
@ -31,5 +31,6 @@ if(imgui_ADDED)
|
|||
target_include_directories(imgui PUBLIC "${imgui_BINARY_DIR}/include" "${CMAKE_CURRENT_SOURCE_DIR}/imgui_config")
|
||||
target_compile_definitions(imgui PUBLIC IMGUI_USER_CONFIG="srb2_imconfig.h")
|
||||
target_compile_features(imgui PUBLIC cxx_std_11)
|
||||
target_link_libraries(imgui PRIVATE stb_rect_pack)
|
||||
add_library(imgui::imgui ALIAS imgui)
|
||||
endif()
|
||||
|
|
|
|||
1
thirdparty/imgui_config/srb2_imconfig.h
vendored
1
thirdparty/imgui_config/srb2_imconfig.h
vendored
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS
|
||||
#define IMGUI_DISABLE_OBSOLETE_KEYIO
|
||||
#define IMGUI_DISABLE_STB_RECT_PACK_IMPLEMENTATION
|
||||
|
||||
// We provide needed functionalities provided by default win32 impls through the interface layer
|
||||
#define IMGUI_DISABLE_WIN32_FUNCTIONS
|
||||
|
|
|
|||
3
thirdparty/stb_rect_pack/CMakeLists.txt
vendored
Normal file
3
thirdparty/stb_rect_pack/CMakeLists.txt
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Update from https://github.com/nothings/stb
|
||||
add_library(stb_rect_pack STATIC stb_rect_pack.c include/stb_rect_pack.h)
|
||||
target_include_directories(stb_rect_pack PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
|
||||
623
thirdparty/stb_rect_pack/include/stb_rect_pack.h
vendored
Normal file
623
thirdparty/stb_rect_pack/include/stb_rect_pack.h
vendored
Normal file
|
|
@ -0,0 +1,623 @@
|
|||
// stb_rect_pack.h - v1.01 - public domain - rectangle packing
|
||||
// Sean Barrett 2014
|
||||
//
|
||||
// Useful for e.g. packing rectangular textures into an atlas.
|
||||
// Does not do rotation.
|
||||
//
|
||||
// Before #including,
|
||||
//
|
||||
// #define STB_RECT_PACK_IMPLEMENTATION
|
||||
//
|
||||
// in the file that you want to have the implementation.
|
||||
//
|
||||
// Not necessarily the awesomest packing method, but better than
|
||||
// the totally naive one in stb_truetype (which is primarily what
|
||||
// this is meant to replace).
|
||||
//
|
||||
// Has only had a few tests run, may have issues.
|
||||
//
|
||||
// More docs to come.
|
||||
//
|
||||
// No memory allocations; uses qsort() and assert() from stdlib.
|
||||
// Can override those by defining STBRP_SORT and STBRP_ASSERT.
|
||||
//
|
||||
// This library currently uses the Skyline Bottom-Left algorithm.
|
||||
//
|
||||
// Please note: better rectangle packers are welcome! Please
|
||||
// implement them to the same API, but with a different init
|
||||
// function.
|
||||
//
|
||||
// Credits
|
||||
//
|
||||
// Library
|
||||
// Sean Barrett
|
||||
// Minor features
|
||||
// Martins Mozeiko
|
||||
// github:IntellectualKitty
|
||||
//
|
||||
// Bugfixes / warning fixes
|
||||
// Jeremy Jaussaud
|
||||
// Fabian Giesen
|
||||
//
|
||||
// Version history:
|
||||
//
|
||||
// 1.01 (2021-07-11) always use large rect mode, expose STBRP__MAXVAL in public section
|
||||
// 1.00 (2019-02-25) avoid small space waste; gracefully fail too-wide rectangles
|
||||
// 0.99 (2019-02-07) warning fixes
|
||||
// 0.11 (2017-03-03) return packing success/fail result
|
||||
// 0.10 (2016-10-25) remove cast-away-const to avoid warnings
|
||||
// 0.09 (2016-08-27) fix compiler warnings
|
||||
// 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0)
|
||||
// 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0)
|
||||
// 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort
|
||||
// 0.05: added STBRP_ASSERT to allow replacing assert
|
||||
// 0.04: fixed minor bug in STBRP_LARGE_RECTS support
|
||||
// 0.01: initial release
|
||||
//
|
||||
// LICENSE
|
||||
//
|
||||
// See end of file for license information.
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// INCLUDE SECTION
|
||||
//
|
||||
|
||||
#ifndef STB_INCLUDE_STB_RECT_PACK_H
|
||||
#define STB_INCLUDE_STB_RECT_PACK_H
|
||||
|
||||
#define STB_RECT_PACK_VERSION 1
|
||||
|
||||
#ifdef STBRP_STATIC
|
||||
#define STBRP_DEF static
|
||||
#else
|
||||
#define STBRP_DEF extern
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct stbrp_context stbrp_context;
|
||||
typedef struct stbrp_node stbrp_node;
|
||||
typedef struct stbrp_rect stbrp_rect;
|
||||
|
||||
typedef int stbrp_coord;
|
||||
|
||||
#define STBRP__MAXVAL 0x7fffffff
|
||||
// Mostly for internal use, but this is the maximum supported coordinate value.
|
||||
|
||||
STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects);
|
||||
// Assign packed locations to rectangles. The rectangles are of type
|
||||
// 'stbrp_rect' defined below, stored in the array 'rects', and there
|
||||
// are 'num_rects' many of them.
|
||||
//
|
||||
// Rectangles which are successfully packed have the 'was_packed' flag
|
||||
// set to a non-zero value and 'x' and 'y' store the minimum location
|
||||
// on each axis (i.e. bottom-left in cartesian coordinates, top-left
|
||||
// if you imagine y increasing downwards). Rectangles which do not fit
|
||||
// have the 'was_packed' flag set to 0.
|
||||
//
|
||||
// You should not try to access the 'rects' array from another thread
|
||||
// while this function is running, as the function temporarily reorders
|
||||
// the array while it executes.
|
||||
//
|
||||
// To pack into another rectangle, you need to call stbrp_init_target
|
||||
// again. To continue packing into the same rectangle, you can call
|
||||
// this function again. Calling this multiple times with multiple rect
|
||||
// arrays will probably produce worse packing results than calling it
|
||||
// a single time with the full rectangle array, but the option is
|
||||
// available.
|
||||
//
|
||||
// The function returns 1 if all of the rectangles were successfully
|
||||
// packed and 0 otherwise.
|
||||
|
||||
struct stbrp_rect
|
||||
{
|
||||
// reserved for your use:
|
||||
int id;
|
||||
|
||||
// input:
|
||||
stbrp_coord w, h;
|
||||
|
||||
// output:
|
||||
stbrp_coord x, y;
|
||||
int was_packed; // non-zero if valid packing
|
||||
|
||||
}; // 16 bytes, nominally
|
||||
|
||||
|
||||
STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes);
|
||||
// Initialize a rectangle packer to:
|
||||
// pack a rectangle that is 'width' by 'height' in dimensions
|
||||
// using temporary storage provided by the array 'nodes', which is 'num_nodes' long
|
||||
//
|
||||
// You must call this function every time you start packing into a new target.
|
||||
//
|
||||
// There is no "shutdown" function. The 'nodes' memory must stay valid for
|
||||
// the following stbrp_pack_rects() call (or calls), but can be freed after
|
||||
// the call (or calls) finish.
|
||||
//
|
||||
// Note: to guarantee best results, either:
|
||||
// 1. make sure 'num_nodes' >= 'width'
|
||||
// or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1'
|
||||
//
|
||||
// If you don't do either of the above things, widths will be quantized to multiples
|
||||
// of small integers to guarantee the algorithm doesn't run out of temporary storage.
|
||||
//
|
||||
// If you do #2, then the non-quantized algorithm will be used, but the algorithm
|
||||
// may run out of temporary storage and be unable to pack some rectangles.
|
||||
|
||||
STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem);
|
||||
// Optionally call this function after init but before doing any packing to
|
||||
// change the handling of the out-of-temp-memory scenario, described above.
|
||||
// If you call init again, this will be reset to the default (false).
|
||||
|
||||
|
||||
STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic);
|
||||
// Optionally select which packing heuristic the library should use. Different
|
||||
// heuristics will produce better/worse results for different data sets.
|
||||
// If you call init again, this will be reset to the default.
|
||||
|
||||
enum
|
||||
{
|
||||
STBRP_HEURISTIC_Skyline_default=0,
|
||||
STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default,
|
||||
STBRP_HEURISTIC_Skyline_BF_sortHeight
|
||||
};
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// the details of the following structures don't matter to you, but they must
|
||||
// be visible so you can handle the memory allocations for them
|
||||
|
||||
struct stbrp_node
|
||||
{
|
||||
stbrp_coord x,y;
|
||||
stbrp_node *next;
|
||||
};
|
||||
|
||||
struct stbrp_context
|
||||
{
|
||||
int width;
|
||||
int height;
|
||||
int align;
|
||||
int init_mode;
|
||||
int heuristic;
|
||||
int num_nodes;
|
||||
stbrp_node *active_head;
|
||||
stbrp_node *free_head;
|
||||
stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2'
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IMPLEMENTATION SECTION
|
||||
//
|
||||
|
||||
#ifdef STB_RECT_PACK_IMPLEMENTATION
|
||||
#ifndef STBRP_SORT
|
||||
#include <stdlib.h>
|
||||
#define STBRP_SORT qsort
|
||||
#endif
|
||||
|
||||
#ifndef STBRP_ASSERT
|
||||
#include <assert.h>
|
||||
#define STBRP_ASSERT assert
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#define STBRP__NOTUSED(v) (void)(v)
|
||||
#define STBRP__CDECL __cdecl
|
||||
#else
|
||||
#define STBRP__NOTUSED(v) (void)sizeof(v)
|
||||
#define STBRP__CDECL
|
||||
#endif
|
||||
|
||||
enum
|
||||
{
|
||||
STBRP__INIT_skyline = 1
|
||||
};
|
||||
|
||||
STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic)
|
||||
{
|
||||
switch (context->init_mode) {
|
||||
case STBRP__INIT_skyline:
|
||||
STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight);
|
||||
context->heuristic = heuristic;
|
||||
break;
|
||||
default:
|
||||
STBRP_ASSERT(0);
|
||||
}
|
||||
}
|
||||
|
||||
STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem)
|
||||
{
|
||||
if (allow_out_of_mem)
|
||||
// if it's ok to run out of memory, then don't bother aligning them;
|
||||
// this gives better packing, but may fail due to OOM (even though
|
||||
// the rectangles easily fit). @TODO a smarter approach would be to only
|
||||
// quantize once we've hit OOM, then we could get rid of this parameter.
|
||||
context->align = 1;
|
||||
else {
|
||||
// if it's not ok to run out of memory, then quantize the widths
|
||||
// so that num_nodes is always enough nodes.
|
||||
//
|
||||
// I.e. num_nodes * align >= width
|
||||
// align >= width / num_nodes
|
||||
// align = ceil(width/num_nodes)
|
||||
|
||||
context->align = (context->width + context->num_nodes-1) / context->num_nodes;
|
||||
}
|
||||
}
|
||||
|
||||
STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i=0; i < num_nodes-1; ++i)
|
||||
nodes[i].next = &nodes[i+1];
|
||||
nodes[i].next = NULL;
|
||||
context->init_mode = STBRP__INIT_skyline;
|
||||
context->heuristic = STBRP_HEURISTIC_Skyline_default;
|
||||
context->free_head = &nodes[0];
|
||||
context->active_head = &context->extra[0];
|
||||
context->width = width;
|
||||
context->height = height;
|
||||
context->num_nodes = num_nodes;
|
||||
stbrp_setup_allow_out_of_mem(context, 0);
|
||||
|
||||
// node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly)
|
||||
context->extra[0].x = 0;
|
||||
context->extra[0].y = 0;
|
||||
context->extra[0].next = &context->extra[1];
|
||||
context->extra[1].x = (stbrp_coord) width;
|
||||
context->extra[1].y = (1<<30);
|
||||
context->extra[1].next = NULL;
|
||||
}
|
||||
|
||||
// find minimum y position if it starts at x1
|
||||
static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste)
|
||||
{
|
||||
stbrp_node *node = first;
|
||||
int x1 = x0 + width;
|
||||
int min_y, visited_width, waste_area;
|
||||
|
||||
STBRP__NOTUSED(c);
|
||||
|
||||
STBRP_ASSERT(first->x <= x0);
|
||||
|
||||
#if 0
|
||||
// skip in case we're past the node
|
||||
while (node->next->x <= x0)
|
||||
++node;
|
||||
#else
|
||||
STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency
|
||||
#endif
|
||||
|
||||
STBRP_ASSERT(node->x <= x0);
|
||||
|
||||
min_y = 0;
|
||||
waste_area = 0;
|
||||
visited_width = 0;
|
||||
while (node->x < x1) {
|
||||
if (node->y > min_y) {
|
||||
// raise min_y higher.
|
||||
// we've accounted for all waste up to min_y,
|
||||
// but we'll now add more waste for everything we've visted
|
||||
waste_area += visited_width * (node->y - min_y);
|
||||
min_y = node->y;
|
||||
// the first time through, visited_width might be reduced
|
||||
if (node->x < x0)
|
||||
visited_width += node->next->x - x0;
|
||||
else
|
||||
visited_width += node->next->x - node->x;
|
||||
} else {
|
||||
// add waste area
|
||||
int under_width = node->next->x - node->x;
|
||||
if (under_width + visited_width > width)
|
||||
under_width = width - visited_width;
|
||||
waste_area += under_width * (min_y - node->y);
|
||||
visited_width += under_width;
|
||||
}
|
||||
node = node->next;
|
||||
}
|
||||
|
||||
*pwaste = waste_area;
|
||||
return min_y;
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int x,y;
|
||||
stbrp_node **prev_link;
|
||||
} stbrp__findresult;
|
||||
|
||||
static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height)
|
||||
{
|
||||
int best_waste = (1<<30), best_x, best_y = (1 << 30);
|
||||
stbrp__findresult fr;
|
||||
stbrp_node **prev, *node, *tail, **best = NULL;
|
||||
|
||||
// align to multiple of c->align
|
||||
width = (width + c->align - 1);
|
||||
width -= width % c->align;
|
||||
STBRP_ASSERT(width % c->align == 0);
|
||||
|
||||
// if it can't possibly fit, bail immediately
|
||||
if (width > c->width || height > c->height) {
|
||||
fr.prev_link = NULL;
|
||||
fr.x = fr.y = 0;
|
||||
return fr;
|
||||
}
|
||||
|
||||
node = c->active_head;
|
||||
prev = &c->active_head;
|
||||
while (node->x + width <= c->width) {
|
||||
int y,waste;
|
||||
y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste);
|
||||
if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL
|
||||
// bottom left
|
||||
if (y < best_y) {
|
||||
best_y = y;
|
||||
best = prev;
|
||||
}
|
||||
} else {
|
||||
// best-fit
|
||||
if (y + height <= c->height) {
|
||||
// can only use it if it first vertically
|
||||
if (y < best_y || (y == best_y && waste < best_waste)) {
|
||||
best_y = y;
|
||||
best_waste = waste;
|
||||
best = prev;
|
||||
}
|
||||
}
|
||||
}
|
||||
prev = &node->next;
|
||||
node = node->next;
|
||||
}
|
||||
|
||||
best_x = (best == NULL) ? 0 : (*best)->x;
|
||||
|
||||
// if doing best-fit (BF), we also have to try aligning right edge to each node position
|
||||
//
|
||||
// e.g, if fitting
|
||||
//
|
||||
// ____________________
|
||||
// |____________________|
|
||||
//
|
||||
// into
|
||||
//
|
||||
// | |
|
||||
// | ____________|
|
||||
// |____________|
|
||||
//
|
||||
// then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned
|
||||
//
|
||||
// This makes BF take about 2x the time
|
||||
|
||||
if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) {
|
||||
tail = c->active_head;
|
||||
node = c->active_head;
|
||||
prev = &c->active_head;
|
||||
// find first node that's admissible
|
||||
while (tail->x < width)
|
||||
tail = tail->next;
|
||||
while (tail) {
|
||||
int xpos = tail->x - width;
|
||||
int y,waste;
|
||||
STBRP_ASSERT(xpos >= 0);
|
||||
// find the left position that matches this
|
||||
while (node->next->x <= xpos) {
|
||||
prev = &node->next;
|
||||
node = node->next;
|
||||
}
|
||||
STBRP_ASSERT(node->next->x > xpos && node->x <= xpos);
|
||||
y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste);
|
||||
if (y + height <= c->height) {
|
||||
if (y <= best_y) {
|
||||
if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) {
|
||||
best_x = xpos;
|
||||
STBRP_ASSERT(y <= best_y);
|
||||
best_y = y;
|
||||
best_waste = waste;
|
||||
best = prev;
|
||||
}
|
||||
}
|
||||
}
|
||||
tail = tail->next;
|
||||
}
|
||||
}
|
||||
|
||||
fr.prev_link = best;
|
||||
fr.x = best_x;
|
||||
fr.y = best_y;
|
||||
return fr;
|
||||
}
|
||||
|
||||
static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height)
|
||||
{
|
||||
// find best position according to heuristic
|
||||
stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height);
|
||||
stbrp_node *node, *cur;
|
||||
|
||||
// bail if:
|
||||
// 1. it failed
|
||||
// 2. the best node doesn't fit (we don't always check this)
|
||||
// 3. we're out of memory
|
||||
if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) {
|
||||
res.prev_link = NULL;
|
||||
return res;
|
||||
}
|
||||
|
||||
// on success, create new node
|
||||
node = context->free_head;
|
||||
node->x = (stbrp_coord) res.x;
|
||||
node->y = (stbrp_coord) (res.y + height);
|
||||
|
||||
context->free_head = node->next;
|
||||
|
||||
// insert the new node into the right starting point, and
|
||||
// let 'cur' point to the remaining nodes needing to be
|
||||
// stiched back in
|
||||
|
||||
cur = *res.prev_link;
|
||||
if (cur->x < res.x) {
|
||||
// preserve the existing one, so start testing with the next one
|
||||
stbrp_node *next = cur->next;
|
||||
cur->next = node;
|
||||
cur = next;
|
||||
} else {
|
||||
*res.prev_link = node;
|
||||
}
|
||||
|
||||
// from here, traverse cur and free the nodes, until we get to one
|
||||
// that shouldn't be freed
|
||||
while (cur->next && cur->next->x <= res.x + width) {
|
||||
stbrp_node *next = cur->next;
|
||||
// move the current node to the free list
|
||||
cur->next = context->free_head;
|
||||
context->free_head = cur;
|
||||
cur = next;
|
||||
}
|
||||
|
||||
// stitch the list back in
|
||||
node->next = cur;
|
||||
|
||||
if (cur->x < res.x + width)
|
||||
cur->x = (stbrp_coord) (res.x + width);
|
||||
|
||||
#ifdef _DEBUG
|
||||
cur = context->active_head;
|
||||
while (cur->x < context->width) {
|
||||
STBRP_ASSERT(cur->x < cur->next->x);
|
||||
cur = cur->next;
|
||||
}
|
||||
STBRP_ASSERT(cur->next == NULL);
|
||||
|
||||
{
|
||||
int count=0;
|
||||
cur = context->active_head;
|
||||
while (cur) {
|
||||
cur = cur->next;
|
||||
++count;
|
||||
}
|
||||
cur = context->free_head;
|
||||
while (cur) {
|
||||
cur = cur->next;
|
||||
++count;
|
||||
}
|
||||
STBRP_ASSERT(count == context->num_nodes+2);
|
||||
}
|
||||
#endif
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static int STBRP__CDECL rect_height_compare(const void *a, const void *b)
|
||||
{
|
||||
const stbrp_rect *p = (const stbrp_rect *) a;
|
||||
const stbrp_rect *q = (const stbrp_rect *) b;
|
||||
if (p->h > q->h)
|
||||
return -1;
|
||||
if (p->h < q->h)
|
||||
return 1;
|
||||
return (p->w > q->w) ? -1 : (p->w < q->w);
|
||||
}
|
||||
|
||||
static int STBRP__CDECL rect_original_order(const void *a, const void *b)
|
||||
{
|
||||
const stbrp_rect *p = (const stbrp_rect *) a;
|
||||
const stbrp_rect *q = (const stbrp_rect *) b;
|
||||
return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed);
|
||||
}
|
||||
|
||||
STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects)
|
||||
{
|
||||
int i, all_rects_packed = 1;
|
||||
|
||||
// we use the 'was_packed' field internally to allow sorting/unsorting
|
||||
for (i=0; i < num_rects; ++i) {
|
||||
rects[i].was_packed = i;
|
||||
}
|
||||
|
||||
// sort according to heuristic
|
||||
STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare);
|
||||
|
||||
for (i=0; i < num_rects; ++i) {
|
||||
if (rects[i].w == 0 || rects[i].h == 0) {
|
||||
rects[i].x = rects[i].y = 0; // empty rect needs no space
|
||||
} else {
|
||||
stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h);
|
||||
if (fr.prev_link) {
|
||||
rects[i].x = (stbrp_coord) fr.x;
|
||||
rects[i].y = (stbrp_coord) fr.y;
|
||||
} else {
|
||||
rects[i].x = rects[i].y = STBRP__MAXVAL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// unsort
|
||||
STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order);
|
||||
|
||||
// set was_packed flags and all_rects_packed status
|
||||
for (i=0; i < num_rects; ++i) {
|
||||
rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL);
|
||||
if (!rects[i].was_packed)
|
||||
all_rects_packed = 0;
|
||||
}
|
||||
|
||||
// return the all_rects_packed status
|
||||
return all_rects_packed;
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
------------------------------------------------------------------------------
|
||||
This software is available under 2 licenses -- choose whichever you prefer.
|
||||
------------------------------------------------------------------------------
|
||||
ALTERNATIVE A - MIT License
|
||||
Copyright (c) 2017 Sean Barrett
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
------------------------------------------------------------------------------
|
||||
ALTERNATIVE B - Public Domain (www.unlicense.org)
|
||||
This is free and unencumbered software released into the public domain.
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
|
||||
software, either in source code form or as a compiled binary, for any purpose,
|
||||
commercial or non-commercial, and by any means.
|
||||
In jurisdictions that recognize copyright laws, the author or authors of this
|
||||
software dedicate any and all copyright interest in the software to the public
|
||||
domain. We make this dedication for the benefit of the public at large and to
|
||||
the detriment of our heirs and successors. We intend this dedication to be an
|
||||
overt act of relinquishment in perpetuity of all present and future rights to
|
||||
this software under copyright law.
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
------------------------------------------------------------------------------
|
||||
*/
|
||||
2
thirdparty/stb_rect_pack/stb_rect_pack.c
vendored
Normal file
2
thirdparty/stb_rect_pack/stb_rect_pack.c
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
#define STB_RECT_PACK_IMPLEMENTATION
|
||||
#include "include/stb_rect_pack.h"
|
||||
Loading…
Add table
Reference in a new issue