Merge branch 'shader-postproc' into 'master'

Shader-based postimg effects

See merge request KartKrew/Kart!1138
This commit is contained in:
Eidolon 2023-04-04 03:37:43 +00:00
commit 91a6bcf8be
17 changed files with 813 additions and 319 deletions

View file

@ -132,6 +132,12 @@ public:
}
}
void clear()
{
arr_ = {{}};
size_ = 0;
}
constexpr T* begin() noexcept { return &arr_[0]; }
constexpr const T* begin() const noexcept { return cbegin(); }

View file

@ -606,9 +606,6 @@ static void D_Display(void)
for (i = 0; i <= r_splitscreen; i++)
{
R_ApplyViewMorph(i);
if (postimgtype[i])
V_DoPostProcessor(i, postimgtype[i], postimgparam[i]);
}
}

View file

@ -1,4 +1,6 @@
target_sources(SRB2SDL2 PRIVATE
pass_blit_postimg_screens.cpp
pass_blit_postimg_screens.hpp
pass_blit_rect.cpp
pass_blit_rect.hpp
pass_imgui.cpp

View file

@ -0,0 +1,253 @@
// 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_postimg_screens.hpp"
#include <glm/mat3x3.hpp>
#include <glm/mat4x4.hpp>
#include <glm/vec3.hpp>
#include <glm/vec4.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include "../p_tick.h"
#include "../i_time.h"
#include "../screen.h"
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 PipelineDesc kPostimgPipelineDesc =
{
PipelineProgram::kPostimg,
{{{sizeof(BlitVertex)}}, {{VertexAttributeName::kPosition, 0, 0}, {VertexAttributeName::kTexCoord0, 0, 12}}},
{{{{UniformName::kTime, UniformName::kProjection, UniformName::kModelView, UniformName::kTexCoord0Transform, UniformName::kTexCoord0Min, UniformName::kTexCoord0Max, UniformName::kPostimgWater, UniformName::kPostimgHeat}}}},
{{SamplerName::kSampler0}},
std::nullopt,
{std::nullopt, {true, true, true, true}},
PrimitiveType::kTriangles,
CullMode::kNone,
FaceWinding::kCounterClockwise,
{0.0, 0.0, 0.0, 1.0}
};
static const PipelineDesc kPostimgIndexedPipelineDesc =
{
PipelineProgram::kPostimg,
{{{sizeof(BlitVertex)}}, {{VertexAttributeName::kPosition, 0, 0}, {VertexAttributeName::kTexCoord0, 0, 12}}},
{{{{UniformName::kTime, UniformName::kProjection, UniformName::kModelView, UniformName::kTexCoord0Transform, UniformName::kTexCoord0Min, UniformName::kTexCoord0Max, UniformName::kPostimgWater, UniformName::kPostimgHeat}}}},
{{SamplerName::kSampler0, SamplerName::kSampler1}},
std::nullopt,
{std::nullopt, {true, true, true, true}},
PrimitiveType::kTriangles,
CullMode::kNone,
FaceWinding::kCounterClockwise,
{0.0, 0.0, 0.0, 1.0}
};
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};
BlitPostimgScreens::BlitPostimgScreens(const std::shared_ptr<MainPaletteManager>& palette_mgr)
: palette_mgr_(palette_mgr)
{
}
BlitPostimgScreens::~BlitPostimgScreens() = default;
void BlitPostimgScreens::prepass(Rhi& rhi)
{
if (!renderpass_)
{
renderpass_ = rhi.create_render_pass(
{
false,
AttachmentLoadOp::kClear,
AttachmentStoreOp::kStore,
AttachmentLoadOp::kDontCare,
AttachmentStoreOp::kDontCare,
AttachmentLoadOp::kDontCare,
AttachmentStoreOp::kDontCare
}
);
}
if (!pipeline_)
{
pipeline_ = rhi.create_pipeline(kPostimgPipelineDesc);
}
if (!indexed_pipeline_)
{
indexed_pipeline_ = rhi.create_pipeline(kPostimgIndexedPipelineDesc);
}
if (!quad_vbo_)
{
quad_vbo_ = rhi.create_buffer({sizeof(kVerts), BufferType::kVertexBuffer, BufferUsage::kImmutable});
upload_quad_buffer_ = true;
}
if (!quad_ibo_)
{
quad_ibo_ = rhi.create_buffer({sizeof(kIndices), BufferType::kIndexBuffer, BufferUsage::kImmutable});
upload_quad_buffer_ = true;
}
screen_data_.clear();
}
static Rect get_screen_viewport(uint32_t screen, uint32_t screens, uint32_t w, uint32_t h)
{
switch (screens)
{
case 1:
return {0, 0, w, h};
case 2:
return {0, screen == 1 ? (static_cast<int32_t>(h) / 2) : 0, w, (h / 2)};
default:
switch (screen)
{
case 2:
return {0, 0, w / 2, h / 2};
case 3:
return {static_cast<int32_t>(w) / 2, 0, w / 2, h / 2};
case 0:
return {0, static_cast<int32_t>(h) / 2, w / 2, h / 2};
case 1:
return {static_cast<int32_t>(w) / 2, static_cast<int32_t>(h) / 2, w / 2, h / 2};
}
}
return {0, 0, w, h};
}
void BlitPostimgScreens::transfer(Rhi& rhi, Handle<TransferContext> ctx)
{
// Upload needed buffers
if (upload_quad_buffer_)
{
rhi.update_buffer(ctx, quad_vbo_, 0, tcb::as_bytes(tcb::span(kVerts)));
rhi.update_buffer(ctx, quad_ibo_, 0, tcb::as_bytes(tcb::span(kIndices)));
upload_quad_buffer_ = false;
}
for (uint32_t i = 0; i < screens_; i++)
{
BlitPostimgScreens::ScreenConfig& screen_config = screen_configs_[i];
BlitPostimgScreens::ScreenData data {};
if (screen_config.indexed)
{
data.pipeline = indexed_pipeline_;
}
else
{
data.pipeline = pipeline_;
}
VertexAttributeBufferBinding vertex_bindings[] = {{0, quad_vbo_}};
TextureBinding sampler_bindings[] =
{
{SamplerName::kSampler0, screen_config.source},
{SamplerName::kSampler1, palette_mgr_->palette()}
};
data.binding_set = rhi.create_binding_set(
ctx,
data.pipeline,
{
vertex_bindings,
tcb::span(sampler_bindings, screen_config.indexed ? 2 : 1)
}
);
glm::mat4 projection = glm::scale(glm::identity<glm::mat4>(), glm::vec3(2.f, -2.f, 1.f));
glm::mat4 modelview = glm::identity<glm::mat4>();
glm::vec2 flip_mirror_uv_displace {0.0, 0.0};
if (screen_config.post.mirror)
{
flip_mirror_uv_displace.x = 1 - (1 - screen_config.uv_size.x);
}
if (screen_config.post.flip)
{
flip_mirror_uv_displace.y = 1 - (1 - screen_config.uv_size.y);
}
glm::mat3 texcoord_transform =
{
glm::vec3(screen_config.uv_size.x * (screen_config.post.mirror ? -1 : 1), 0.0, 0.0),
glm::vec3(0.0, screen_config.uv_size.y * (screen_config.post.flip ? -1 : 1), 0.0),
glm::vec3(screen_config.uv_offset + flip_mirror_uv_displace, 1.0)
};
glm::vec2 texcoord_min = screen_config.uv_offset;
glm::vec2 texcoord_max = screen_config.uv_offset + screen_config.uv_size;
UniformVariant uniforms[] =
{
FixedToFloat(g_time.timefrac) + leveltime,
projection,
modelview,
texcoord_transform,
texcoord_min,
texcoord_max,
screen_config.post.water,
screen_config.post.heat
};
data.uniform_set = rhi.create_uniform_set(ctx, {uniforms});
screen_data_[i] = std::move(data);
}
}
void BlitPostimgScreens::graphics(Rhi& rhi, Handle<GraphicsContext> ctx)
{
if (target_)
{
rhi.begin_render_pass(ctx, {renderpass_, target_, std::nullopt, glm::vec4(0.0, 0.0, 0.0, 1.0)});
}
else
{
rhi.begin_default_render_pass(ctx, true);
}
for (uint32_t i = 0; i < screens_; i++)
{
BlitPostimgScreens::ScreenData& data = screen_data_[i];
rhi.bind_pipeline(ctx, data.pipeline);
rhi.set_viewport(ctx, get_screen_viewport(i, screens_, target_ ? target_width_ : vid.width, target_ ? target_height_ : vid.height));
rhi.bind_uniform_set(ctx, 0, data.uniform_set);
rhi.bind_binding_set(ctx, data.binding_set);
rhi.bind_index_buffer(ctx, quad_ibo_);
rhi.draw_indexed(ctx, 6, 0);
}
rhi.end_render_pass(ctx);
}
void BlitPostimgScreens::postpass(Rhi& rhi)
{
}

View file

@ -0,0 +1,101 @@
// 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_POSTIMG_SCREENS__
#define __SRB2_HWR2_PASS_BLIT_POSTIMG_SCREENS__
#include <array>
#include <cstdint>
#include <glm/vec2.hpp>
#include "../rhi/rhi.hpp"
#include "../doomdef.h"
#include "pass.hpp"
#include "pass_resource_managers.hpp"
namespace srb2::hwr2
{
class BlitPostimgScreens : public Pass
{
public:
struct PostImgConfig
{
bool water;
bool heat;
bool flip;
bool mirror;
};
struct ScreenConfig
{
rhi::Handle<rhi::Texture> source;
bool indexed = false;
glm::vec2 uv_offset {};
glm::vec2 uv_size {};
PostImgConfig post;
};
private:
struct ScreenData
{
rhi::Handle<rhi::Pipeline> pipeline;
rhi::Handle<rhi::BindingSet> binding_set;
rhi::Handle<rhi::UniformSet> uniform_set;
};
rhi::Handle<rhi::Pipeline> pipeline_;
rhi::Handle<rhi::Pipeline> indexed_pipeline_;
rhi::Handle<rhi::RenderPass> renderpass_;
rhi::Handle<rhi::Buffer> quad_vbo_;
rhi::Handle<rhi::Buffer> quad_ibo_;
bool upload_quad_buffer_;
uint32_t screens_;
std::array<ScreenConfig, 4> screen_configs_;
srb2::StaticVec<ScreenData, 4> screen_data_;
rhi::Handle<rhi::Texture> target_;
uint32_t target_width_;
uint32_t target_height_;
std::shared_ptr<MainPaletteManager> palette_mgr_;
public:
BlitPostimgScreens(const std::shared_ptr<MainPaletteManager>& palette_mgr);
virtual ~BlitPostimgScreens();
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_num_screens(uint32_t screens) noexcept
{
SRB2_ASSERT(screens > 0 && screens <= MAXSPLITSCREENPLAYERS);
screens_ = screens;
}
void set_screen(uint32_t screen_index, const ScreenConfig& config) noexcept
{
SRB2_ASSERT(screen_index < MAXSPLITSCREENPLAYERS);
screen_configs_[screen_index] = config;
}
void set_target(rhi::Handle<rhi::Texture> target, uint32_t width, uint32_t height) noexcept
{
target_ = target;
target_width_ = width;
target_height_ = height;
}
};
}; // namespace srb2::hwr2
#endif // __SRB2_HWR2_PASS_BLIT_POSTIMG_SCREENS__

View file

@ -17,6 +17,7 @@
#include "cxxutil.hpp"
#include "f_finale.h"
#include "hwr2/pass_blit_postimg_screens.hpp"
#include "hwr2/pass_blit_rect.hpp"
#include "hwr2/pass_imgui.hpp"
#include "hwr2/pass_manager.hpp"
@ -205,7 +206,7 @@ static InternalPassData build_pass_manager()
auto basic_rendering = std::make_shared<PassManager>();
auto software_pass = std::make_shared<SoftwarePass>();
auto blit_sw_pass = std::make_shared<BlitRectPass>(palette_manager, true);
auto blit_postimg_screens = std::make_shared<BlitPostimgScreens>(palette_manager);
auto twodee = std::make_shared<TwodeePass>();
twodee->flat_manager_ = flat_texture_manager;
twodee->data_ = make_twodee_pass_data();
@ -222,20 +223,77 @@ static InternalPassData build_pass_manager()
const bool sw_enabled = rendermode == render_soft && gamestate != GS_NULL;
mgr.set_pass_enabled("software", sw_enabled);
mgr.set_pass_enabled("blit_sw_prepare", sw_enabled);
mgr.set_pass_enabled("blit_sw", sw_enabled && !g_wipeskiprender);
mgr.set_pass_enabled("blit_postimg_screens_prepare", sw_enabled);
mgr.set_pass_enabled("blit_postimg_screens", sw_enabled && !g_wipeskiprender);
}
);
basic_rendering->insert("software", software_pass);
basic_rendering->insert(
"blit_sw_prepare",
[blit_sw_pass, software_pass, framebuffer_manager](PassManager&, Rhi&)
"blit_postimg_screens_prepare",
[blit_postimg_screens, 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->main_color(), vid.width, vid.height, false, false);
const bool sw_enabled = rendermode == render_soft && gamestate != GS_NULL;
const int screens = std::clamp(r_splitscreen + 1, 1, MAXSPLITSCREENPLAYERS);
blit_postimg_screens->set_num_screens(screens);
for (int i = 0; i < screens; i++)
{
if (sw_enabled)
{
glm::vec2 uv_offset {0.f, 0.f};
glm::vec2 uv_size {1.f, 1.f};
if (screens > 2)
{
uv_size = glm::vec2(.5f, .5f);
switch (i)
{
case 0:
uv_offset = glm::vec2(0.f, 0.f);
break;
case 1:
uv_offset = glm::vec2(.5f, 0.f);
break;
case 2:
uv_offset = glm::vec2(0.f, .5f);
break;
case 3:
uv_offset = glm::vec2(.5f, .5f);
break;
}
}
else if (screens > 1)
{
uv_size = glm::vec2(1.0, 0.5);
if (i == 1)
{
uv_offset = glm::vec2(0.f, .5f);
}
}
// "You should probably never have more than 3 levels of indentation" -- Eidolon, the author of this
blit_postimg_screens->set_screen(
i,
{
software_pass->screen_texture(),
true,
uv_offset,
uv_size,
{
postimgtype[i] == postimg_water,
postimgtype[i] == postimg_heat,
postimgtype[i] == postimg_flip,
postimgtype[i] == postimg_mirror
}
}
);
}
}
blit_postimg_screens->set_target(framebuffer_manager->main_color(), vid.width, vid.height);
}
);
basic_rendering->insert("blit_sw", blit_sw_pass);
basic_rendering->insert("blit_postimg_screens", blit_postimg_screens);
basic_rendering->insert(
"2d_prepare",

View file

@ -2,6 +2,8 @@ target_sources(SRB2SDL2 PRIVATE
handle.hpp
rhi.cpp
rhi.hpp
shader_load_context.cpp
shader_load_context.hpp
)
add_subdirectory(gl3_core)

View file

@ -20,6 +20,8 @@
#include <glad/gl.h>
#include <glm/gtc/type_ptr.hpp>
#include "../shader_load_context.hpp"
using namespace srb2;
using namespace rhi;
@ -346,12 +348,40 @@ 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::kTexCoord0Min:
return "u_texcoord0_min";
case rhi::UniformName::kTexCoord0Max:
return "u_texcoord0_max";
case rhi::UniformName::kTexCoord1Transform:
return "u_texcoord1_transform";
case rhi::UniformName::kTexCoord1Min:
return "u_texcoord1_min";
case rhi::UniformName::kTexCoord1Max:
return "u_texcoord1_max";
case rhi::UniformName::kSampler0IsIndexedAlpha:
return "u_sampler0_is_indexed_alpha";
case rhi::UniformName::kSampler1IsIndexedAlpha:
return "u_sampler1_is_indexed_alpha";
case rhi::UniformName::kSampler2IsIndexedAlpha:
return "u_sampler2_is_indexed_alpha";
case rhi::UniformName::kSampler3IsIndexedAlpha:
return "u_sampler3_is_indexed_alpha";
case rhi::UniformName::kSampler0Size:
return "u_sampler0_size";
case rhi::UniformName::kSampler1Size:
return "u_sampler1_size";
case rhi::UniformName::kSampler2Size:
return "u_sampler2_size";
case rhi::UniformName::kSampler3Size:
return "u_sampler3_size";
case rhi::UniformName::kWipeColorizeMode:
return "u_wipe_colorize_mode";
case rhi::UniformName::kWipeEncoreSwizzle:
return "u_wipe_encore_swizzle";
case rhi::UniformName::kPostimgWater:
return "u_postimg_water";
case rhi::UniformName::kPostimgHeat:
return "u_postimg_heat";
default:
return nullptr;
}
@ -369,12 +399,40 @@ constexpr const char* map_uniform_enable_define(rhi::UniformName name)
return "ENABLE_U_MODELVIEW";
case rhi::UniformName::kTexCoord0Transform:
return "ENABLE_U_TEXCOORD0_TRANSFORM";
case rhi::UniformName::kTexCoord0Min:
return "ENABLE_U_TEXCOORD0_MIN";
case rhi::UniformName::kTexCoord0Max:
return "ENABLE_U_TEXCOORD0_MAX";
case rhi::UniformName::kTexCoord1Transform:
return "ENABLE_U_TEXCOORD1_TRANSFORM";
case rhi::UniformName::kTexCoord1Min:
return "ENABLE_U_TEXCOORD1_MIN";
case rhi::UniformName::kTexCoord1Max:
return "ENABLE_U_TEXCOORD1_MAX";
case rhi::UniformName::kSampler0IsIndexedAlpha:
return "ENABLE_U_SAMPLER0_IS_INDEXED_ALPHA";
case rhi::UniformName::kSampler1IsIndexedAlpha:
return "ENABLE_U_SAMPLER1_IS_INDEXED_ALPHA";
case rhi::UniformName::kSampler2IsIndexedAlpha:
return "ENABLE_U_SAMPLER2_IS_INDEXED_ALPHA";
case rhi::UniformName::kSampler3IsIndexedAlpha:
return "ENABLE_U_SAMPLER3_IS_INDEXED_ALPHA";
case rhi::UniformName::kSampler0Size:
return "ENABLE_U_SAMPLER0_SIZE";
case rhi::UniformName::kSampler1Size:
return "ENABLE_U_SAMPLER1_SIZE";
case rhi::UniformName::kSampler2Size:
return "ENABLE_U_SAMPLER2_SIZE";
case rhi::UniformName::kSampler3Size:
return "ENABLE_U_SAMPLER3_SIZE";
case rhi::UniformName::kWipeColorizeMode:
return "ENABLE_U_WIPE_COLORIZE_MODE";
case rhi::UniformName::kWipeEncoreSwizzle:
return "ENABLE_U_WIPE_ENCORE_SWIZZLE";
case rhi::UniformName::kPostimgWater:
return "ENABLE_U_POSTIMG_WATER";
case rhi::UniformName::kPostimgHeat:
return "ENABLE_U_POSTIMG_HEAT";
default:
return nullptr;
}
@ -812,6 +870,7 @@ rhi::Handle<rhi::Renderbuffer> GlCoreRhi::create_renderbuffer(const rhi::Renderb
GlCoreRenderbuffer rb;
rb.renderbuffer = name;
rb.desc = desc;
return renderbuffer_slab_.insert(std::move(rb));
}
@ -838,112 +897,82 @@ rhi::Handle<rhi::Pipeline> GlCoreRhi::create_pipeline(const PipelineDesc& desc)
GLuint program = 0;
GlCorePipeline pipeline;
auto [vert_src, frag_src] = platform_->find_shader_sources(desc.program);
auto [vert_srcs, frag_srcs] = platform_->find_shader_sources(desc.program);
// TODO make use of multiple source files.
// In Khronos Group's brilliance, #version is required to be the first directive,
// but we need to insert #defines in-between.
std::string vert_src_processed;
std::string::size_type string_i = 0;
do
// Process vertex shader sources
std::vector<const char*> vert_sources;
ShaderLoadContext vert_ctx;
vert_ctx.set_version("150 core");
for (auto& attribute : desc.vertex_input.attr_layouts)
{
std::string::size_type new_i = vert_src.find('\n', string_i);
if (new_i == std::string::npos)
for (auto const& require_attr : reqs.vertex_input.attributes)
{
break;
}
std::string_view line_view(vert_src.c_str() + string_i, new_i - string_i + 1);
vert_src_processed.append(line_view);
if (line_view.rfind("#version ", 0) == 0)
{
for (auto& attribute : desc.vertex_input.attr_layouts)
if (require_attr.name == attribute.name && !require_attr.required)
{
for (auto const& require_attr : reqs.vertex_input.attributes)
{
if (require_attr.name == attribute.name && !require_attr.required)
{
vert_src_processed.append("#define ");
vert_src_processed.append(map_vertex_attribute_enable_define(attribute.name));
vert_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)
{
vert_src_processed.append("#define ");
vert_src_processed.append(map_uniform_enable_define(uniform));
vert_src_processed.append("\n");
}
}
}
}
vert_ctx.define(map_vertex_attribute_enable_define(attribute.name));
}
}
string_i = new_i + 1;
} while (string_i != std::string::npos);
std::string frag_src_processed;
string_i = 0;
do
}
for (auto& uniform_group : desc.uniform_input.enabled_uniforms)
{
std::string::size_type new_i = frag_src.find('\n', string_i);
if (new_i == std::string::npos)
for (auto& uniform : uniform_group)
{
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& req_uni_group : reqs.uniforms.uniform_groups)
{
for (auto const& require_sampler : reqs.samplers.samplers)
for (auto const& req_uni : req_uni_group)
{
if (sampler == require_sampler.name && !require_sampler.required)
if (req_uni.name == uniform && !req_uni.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");
}
}
vert_ctx.define(map_uniform_enable_define(uniform));
}
}
}
}
string_i = new_i + 1;
} while (string_i != std::string::npos);
}
for (auto& src : vert_srcs)
{
vert_ctx.add_source(std::move(src));
}
vert_sources = vert_ctx.get_sources_array();
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_processed.c_str()};
const GLint frag_src_arr_lens[1] = {static_cast<GLint>(frag_src_processed.size())};
// Process vertex shader sources
std::vector<const char*> frag_sources;
ShaderLoadContext frag_ctx;
frag_ctx.set_version("150 core");
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_ctx.define(map_sampler_enable_define(sampler));
}
}
}
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_ctx.define(map_uniform_enable_define(uniform));
}
}
}
}
}
for (auto& src : frag_srcs)
{
frag_ctx.add_source(std::move(src));
}
frag_sources = frag_ctx.get_sources_array();
vertex = gl_->CreateShader(GL_VERTEX_SHADER);
gl_->ShaderSource(vertex, 1, vert_src_arr, vert_src_arr_lens);
gl_->ShaderSource(vertex, vert_sources.size(), vert_sources.data(), NULL);
gl_->CompileShader(vertex);
GLint is_compiled = 0;
gl_->GetShaderiv(vertex, GL_COMPILE_STATUS, &is_compiled);
@ -959,7 +988,7 @@ rhi::Handle<rhi::Pipeline> GlCoreRhi::create_pipeline(const PipelineDesc& desc)
);
}
fragment = gl_->CreateShader(GL_FRAGMENT_SHADER);
gl_->ShaderSource(fragment, 1, frag_src_arr, frag_src_arr_lens);
gl_->ShaderSource(fragment, frag_sources.size(), frag_sources.data(), NULL);
gl_->CompileShader(fragment);
gl_->GetShaderiv(vertex, GL_COMPILE_STATUS, &is_compiled);
if (is_compiled == GL_FALSE)
@ -1703,6 +1732,33 @@ void GlCoreRhi::read_pixels(Handle<GraphicsContext> ctx, const Rect& rect, Pixel
gl_->ReadPixels(rect.x, rect.y, rect.w, rect.h, layout, type, out.data());
}
TextureDetails GlCoreRhi::get_texture_details(Handle<Texture> texture)
{
SRB2_ASSERT(texture_slab_.is_valid(texture));
auto& t = texture_slab_[texture];
TextureDetails ret {};
ret.format = t.desc.format;
ret.width = t.desc.width;
ret.height = t.desc.height;
return ret;
}
Rect GlCoreRhi::get_renderbuffer_size(Handle<Renderbuffer> renderbuffer)
{
SRB2_ASSERT(renderbuffer_slab_.is_valid(renderbuffer));
auto& rb = renderbuffer_slab_[renderbuffer];
Rect ret {};
ret.x = 0;
ret.y = 0;
ret.w = rb.desc.width;
ret.h = rb.desc.height;
return ret;
}
void GlCoreRhi::finish()
{
SRB2_ASSERT(graphics_context_active_ == false);

View file

@ -70,7 +70,7 @@ struct GlCorePlatform
virtual ~GlCorePlatform();
virtual void present() = 0;
virtual std::tuple<std::string, std::string> find_shader_sources(PipelineProgram program) = 0;
virtual std::tuple<std::vector<std::string>, std::vector<std::string>> find_shader_sources(PipelineProgram program) = 0;
virtual Rect get_default_framebuffer_dimensions() = 0;
};
@ -94,6 +94,7 @@ struct GlCoreRenderPass : public rhi::RenderPass
struct GlCoreRenderbuffer : public rhi::Renderbuffer
{
uint32_t renderbuffer;
rhi::RenderbufferDesc desc;
};
struct GlCoreUniformSet : public rhi::UniformSet
@ -181,6 +182,9 @@ public:
virtual Handle<Renderbuffer> create_renderbuffer(const RenderbufferDesc& desc) override;
virtual void destroy_renderbuffer(Handle<Renderbuffer> handle) override;
virtual TextureDetails get_texture_details(Handle<Texture> texture) override;
virtual Rect get_renderbuffer_size(Handle<Renderbuffer> renderbuffer) override;
virtual Handle<TransferContext> begin_transfer() override;
virtual void end_transfer(Handle<TransferContext> handle) override;

View file

@ -50,6 +50,23 @@ const ProgramRequirements srb2::rhi::kProgramRequirementsPostprocessWipe = {
{UniformName::kWipeEncoreSwizzle, true}}}}},
ProgramSamplerRequirements {{{SamplerName::kSampler0, true}, {SamplerName::kSampler1, true}, {SamplerName::kSampler2, true}}}};
const ProgramRequirements srb2::rhi::kProgramRequirementsPostimg = {
ProgramVertexInputRequirements {
{ProgramVertexInput {VertexAttributeName::kPosition, VertexAttributeFormat::kFloat3, true},
ProgramVertexInput {VertexAttributeName::kTexCoord0, VertexAttributeFormat::kFloat2, true}}},
ProgramUniformRequirements {
{{
{UniformName::kTime, true},
{UniformName::kProjection, true},
{UniformName::kModelView, true},
{UniformName::kTexCoord0Transform, true},
{UniformName::kTexCoord0Min, true},
{UniformName::kTexCoord0Max, true},
{UniformName::kPostimgWater, true},
{UniformName::kPostimgHeat, true}}}},
ProgramSamplerRequirements {{{SamplerName::kSampler0, true}, {SamplerName::kSampler1, false}}}
};
const ProgramRequirements& rhi::program_requirements_for_program(PipelineProgram program) noexcept
{
switch (program)
@ -60,6 +77,8 @@ const ProgramRequirements& rhi::program_requirements_for_program(PipelineProgram
return kProgramRequirementsUnshadedPaletted;
case PipelineProgram::kPostprocessWipe:
return kProgramRequirementsPostprocessWipe;
case PipelineProgram::kPostimg:
return kProgramRequirementsPostimg;
default:
std::terminate();
}

View file

@ -171,7 +171,8 @@ enum class PipelineProgram
{
kUnshaded,
kUnshadedPaletted,
kPostprocessWipe
kPostprocessWipe,
kPostimg
};
enum class BufferType
@ -201,9 +202,23 @@ enum class UniformName
kModelView,
kProjection,
kTexCoord0Transform,
kTexCoord0Min,
kTexCoord0Max,
kTexCoord1Transform,
kTexCoord1Min,
kTexCoord1Max,
kSampler0IsIndexedAlpha,
kSampler1IsIndexedAlpha,
kSampler2IsIndexedAlpha,
kSampler3IsIndexedAlpha,
kSampler0Size,
kSampler1Size,
kSampler2Size,
kSampler3Size,
kWipeColorizeMode,
kWipeEncoreSwizzle
kWipeEncoreSwizzle,
kPostimgWater,
kPostimgHeat
};
enum class SamplerName
@ -269,6 +284,7 @@ struct ProgramRequirements
extern const ProgramRequirements kProgramRequirementsUnshaded;
extern const ProgramRequirements kProgramRequirementsUnshadedPaletted;
extern const ProgramRequirements kProgramRequirementsPostprocessWipe;
extern const ProgramRequirements kProgramRequirementsPostimg;
const ProgramRequirements& program_requirements_for_program(PipelineProgram program) noexcept;
@ -303,12 +319,40 @@ inline constexpr const UniformFormat uniform_format(UniformName name) noexcept
return UniformFormat::kMat4;
case UniformName::kTexCoord0Transform:
return UniformFormat::kMat3;
case UniformName::kTexCoord0Min:
return UniformFormat::kFloat2;
case UniformName::kTexCoord0Max:
return UniformFormat::kFloat2;
case UniformName::kTexCoord1Transform:
return UniformFormat::kMat3;
case UniformName::kTexCoord1Min:
return UniformFormat::kFloat2;
case UniformName::kTexCoord1Max:
return UniformFormat::kFloat2;
case UniformName::kSampler0IsIndexedAlpha:
return UniformFormat::kInt;
case UniformName::kSampler1IsIndexedAlpha:
return UniformFormat::kInt;
case UniformName::kSampler2IsIndexedAlpha:
return UniformFormat::kInt;
case UniformName::kSampler3IsIndexedAlpha:
return UniformFormat::kInt;
case UniformName::kSampler0Size:
return UniformFormat::kFloat2;
case UniformName::kSampler1Size:
return UniformFormat::kFloat2;
case UniformName::kSampler2Size:
return UniformFormat::kFloat2;
case UniformName::kSampler3Size:
return UniformFormat::kFloat2;
case UniformName::kWipeColorizeMode:
return UniformFormat::kInt;
case UniformName::kWipeEncoreSwizzle:
return UniformFormat::kInt;
case UniformName::kPostimgWater:
return UniformFormat::kInt;
case UniformName::kPostimgHeat:
return UniformFormat::kInt;
default:
return UniformFormat::kFloat;
}
@ -341,7 +385,7 @@ struct UniformInputDesc
struct SamplerInputDesc
{
std::vector<SamplerName> enabled_samplers;
srb2::StaticVec<SamplerName, 4> enabled_samplers;
};
struct ColorMask
@ -535,6 +579,13 @@ struct GraphicsContext
{
};
struct TextureDetails
{
uint32_t width;
uint32_t height;
TextureFormat format;
};
/// @brief The unpack alignment of a row span when uploading pixels to the device.
constexpr const std::size_t kPixelRowUnpackAlignment = 4;
@ -555,6 +606,9 @@ struct Rhi
virtual Handle<Renderbuffer> create_renderbuffer(const RenderbufferDesc& desc) = 0;
virtual void destroy_renderbuffer(Handle<Renderbuffer> handle) = 0;
virtual TextureDetails get_texture_details(Handle<Texture> texture) = 0;
virtual Rect get_renderbuffer_size(Handle<Renderbuffer> renderbuffer) = 0;
virtual Handle<TransferContext> begin_transfer() = 0;
virtual void end_transfer(Handle<TransferContext> handle) = 0;

View file

@ -0,0 +1,51 @@
// 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 "shader_load_context.hpp"
#include <fmt/core.h>
using namespace srb2;
using namespace rhi;
ShaderLoadContext::ShaderLoadContext() = default;
void ShaderLoadContext::set_version(std::string_view version)
{
version_ = fmt::format("#version {}\n", version);
}
void ShaderLoadContext::add_source(const std::string& source)
{
sources_.push_back(source);
}
void ShaderLoadContext::add_source(std::string&& source)
{
sources_.push_back(std::move(source));
}
void ShaderLoadContext::define(std::string_view name)
{
defines_.append(fmt::format("#define {}\n", name));
}
std::vector<const char*> ShaderLoadContext::get_sources_array()
{
std::vector<const char*> ret;
ret.push_back(version_.c_str());
ret.push_back(defines_.c_str());
for (auto& source : sources_)
{
ret.push_back(source.c_str());
}
return ret;
}

View file

@ -0,0 +1,40 @@
// 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_SHADER_LOAD_CONTEXT_HPP__
#define __SRB2_RHI_SHADER_LOAD_CONTEXT_HPP__
#include <string>
#include <string_view>
#include <vector>
namespace srb2::rhi
{
class ShaderLoadContext
{
std::string version_;
std::string defines_;
std::vector<std::string> sources_;
public:
ShaderLoadContext();
void set_version(std::string_view version);
void add_source(const std::string& source);
void add_source(std::string&& source);
void define(std::string_view name);
std::vector<const char*> get_sources_array();
};
}; // namespace srb2::rhi
#endif // __SRB2_RHI_SHADER_LOAD_CONTEXT_HPP__

View file

@ -9,7 +9,11 @@
#include "rhi_gl3_core_platform.hpp"
#include <array>
#include <sstream>
#include <SDL.h>
#include <fmt/core.h>
#include "../cxxutil.hpp"
#include "../w_wad.h"
@ -28,39 +32,77 @@ void SdlGlCorePlatform::present()
SDL_GL_SwapWindow(window);
}
std::tuple<std::string, std::string> SdlGlCorePlatform::find_shader_sources(rhi::PipelineProgram program)
static constexpr const char* pipeline_lump_slug(rhi::PipelineProgram program)
{
const char* vertex_lump_name = nullptr;
const char* fragment_lump_name = nullptr;
switch (program)
{
case rhi::PipelineProgram::kUnshaded:
vertex_lump_name = "rhi_glcore_vertex_unshaded";
fragment_lump_name = "rhi_glcore_fragment_unshaded";
break;
return "unshaded";
case rhi::PipelineProgram::kUnshadedPaletted:
vertex_lump_name = "rhi_glcore_vertex_unshadedpaletted";
fragment_lump_name = "rhi_glcore_fragment_unshadedpaletted";
break;
return "unshadedpaletted";
case rhi::PipelineProgram::kPostprocessWipe:
vertex_lump_name = "rhi_glcore_vertex_postprocesswipe";
fragment_lump_name = "rhi_glcore_fragment_postprocesswipe";
break;
return "postprocesswipe";
case rhi::PipelineProgram::kPostimg:
return "postimg";
default:
std::terminate();
return "";
}
}
static std::array<std::string, 2> glsllist_lump_names(rhi::PipelineProgram program)
{
const char* pipeline_slug = pipeline_lump_slug(program);
std::string vertex_list_name = fmt::format("rhi_glsllist_{}_vertex", pipeline_slug);
std::string fragment_list_name = fmt::format("rhi_glsllist_{}_fragment", pipeline_slug);
return {std::move(vertex_list_name), std::move(fragment_list_name)};
}
static std::vector<std::string> get_sources_from_glsllist_lump(const char* lumpname)
{
lumpnum_t glsllist_lump_num = W_GetNumForLongName(lumpname);
void* glsllist_lump = W_CacheLumpNum(glsllist_lump_num, PU_CACHE);
size_t glsllist_lump_length = W_LumpLength(glsllist_lump_num);
std::istringstream glsllist(std::string(static_cast<const char*>(glsllist_lump), glsllist_lump_length));
std::vector<std::string> sources;
for (std::string line; std::getline(glsllist, line); )
{
if (line.empty())
{
continue;
}
if (line[0] == '#')
{
continue;
}
if (line.back() == '\r')
{
line.pop_back();
}
lumpnum_t source_lump_num = W_GetNumForLongName(line.c_str());
void* source_lump = W_CacheLumpNum(source_lump_num, PU_CACHE);
size_t source_lump_length = W_LumpLength(source_lump_num);
sources.emplace_back(static_cast<const char*>(source_lump), source_lump_length);
}
lumpnum_t vertex_lump_num = W_GetNumForLongName(vertex_lump_name);
lumpnum_t fragment_lump_num = W_GetNumForLongName(fragment_lump_name);
size_t vertex_lump_length = W_LumpLength(vertex_lump_num);
size_t fragment_lump_length = W_LumpLength(fragment_lump_num);
void* vertex_lump = W_CacheLumpNum(vertex_lump_num, PU_CACHE);
void* fragment_lump = W_CacheLumpNum(fragment_lump_num, PU_CACHE);
return sources;
}
std::string vertex_shader(static_cast<const char*>(vertex_lump), vertex_lump_length);
std::string fragment_shader(static_cast<const char*>(fragment_lump), fragment_lump_length);
std::tuple<std::vector<std::string>, std::vector<std::string>>
SdlGlCorePlatform::find_shader_sources(rhi::PipelineProgram program)
{
std::array<std::string, 2> glsllist_names = glsllist_lump_names(program);
return std::make_tuple(std::move(vertex_shader), std::move(fragment_shader));
std::vector<std::string> vertex_sources = get_sources_from_glsllist_lump(glsllist_names[0].c_str());
std::vector<std::string> fragment_sources = get_sources_from_glsllist_lump(glsllist_names[1].c_str());
return std::make_tuple(std::move(vertex_sources), std::move(fragment_sources));
}
rhi::Rect SdlGlCorePlatform::get_default_framebuffer_dimensions()

View file

@ -25,7 +25,8 @@ struct SdlGlCorePlatform final : public GlCorePlatform
virtual ~SdlGlCorePlatform();
virtual void present() override;
virtual std::tuple<std::string, std::string> find_shader_sources(PipelineProgram program) override;
virtual std::tuple<std::vector<std::string>, std::vector<std::string>>
find_shader_sources(PipelineProgram program) override;
virtual Rect get_default_framebuffer_dimensions() override;
};

View file

@ -2898,196 +2898,6 @@ INT32 V_LevelNameHeight(const char *string)
return w;
}
boolean *heatshifter = NULL;
INT32 lastheight = 0;
INT32 heatindex[MAXSPLITSCREENPLAYERS] = {0, 0, 0, 0};
//
// V_DoPostProcessor
//
// Perform a particular image postprocessing function.
//
#include "p_local.h"
void V_DoPostProcessor(INT32 view, postimg_t type, INT32 param)
{
#if NUMSCREENS < 5
// do not enable image post processing for ARM, SH and MIPS CPUs
(void)view;
(void)type;
(void)param;
#else
INT32 yoffset, xoffset;
#ifdef HWRENDER
if (rendermode != render_soft)
return;
#endif
if (view < 0 || view > 3 || view > r_splitscreen)
return;
if ((view == 1 && r_splitscreen == 1) || view >= 2)
yoffset = viewheight;
else
yoffset = 0;
if ((view == 1 || view == 3) && r_splitscreen > 1)
xoffset = viewwidth;
else
xoffset = 0;
if (type == postimg_water)
{
UINT8 *tmpscr = screens[4];
UINT8 *srcscr = screens[0];
INT32 y;
angle_t disStart = (leveltime * 128) & FINEMASK; // in 0 to FINEANGLE
INT32 newpix;
INT32 sine;
//UINT8 *transme = R_GetTranslucencyTable(tr_trans50);
for (y = yoffset; y < yoffset+viewheight; y++)
{
sine = (FINESINE(disStart)*5)>>FRACBITS;
newpix = abs(sine);
if (sine < 0)
{
M_Memcpy(&tmpscr[(y*vid.width)+xoffset+newpix], &srcscr[(y*vid.width)+xoffset], viewwidth-newpix);
// Cleanup edge
while (newpix)
{
tmpscr[(y*vid.width)+xoffset+newpix] = srcscr[(y*vid.width)+xoffset];
newpix--;
}
}
else
{
M_Memcpy(&tmpscr[(y*vid.width)+xoffset+0], &srcscr[(y*vid.width)+xoffset+sine], viewwidth-newpix);
// Cleanup edge
while (newpix)
{
tmpscr[(y*vid.width)+xoffset+viewwidth-newpix] = srcscr[(y*vid.width)+xoffset+(viewwidth-1)];
newpix--;
}
}
/*
Unoptimized version
for (x = 0; x < vid.width*vid.bpp; x++)
{
newpix = (x + sine);
if (newpix < 0)
newpix = 0;
else if (newpix >= vid.width)
newpix = vid.width-1;
tmpscr[y*vid.width + x] = srcscr[y*vid.width+newpix]; // *(transme + (srcscr[y*vid.width+x]<<8) + srcscr[y*vid.width+newpix]);
}*/
disStart += 22;//the offset into the displacement map, increment each game loop
disStart &= FINEMASK; //clip it to FINEMASK
}
VID_BlitLinearScreen(tmpscr+vid.width*vid.bpp*yoffset+xoffset, screens[0]+vid.width*vid.bpp*yoffset+xoffset,
viewwidth*vid.bpp, viewheight, vid.width*vid.bpp, vid.width);
}
else if (type == postimg_motion) // Motion Blur!
{
UINT8 *tmpscr = screens[4];
UINT8 *srcscr = screens[0];
INT32 x, y;
// TODO: Add a postimg_param so that we can pick the translucency level...
UINT8 *transme = R_GetTranslucencyTable(param);
for (y = yoffset; y < yoffset+viewheight; y++)
{
for (x = xoffset; x < xoffset+viewwidth; x++)
{
tmpscr[y*vid.width + x]
= colormaps[*(transme + (srcscr [(y*vid.width)+x ] <<8) + (tmpscr[(y*vid.width)+x]))];
}
}
VID_BlitLinearScreen(tmpscr+vid.width*vid.bpp*yoffset+xoffset, screens[0]+vid.width*vid.bpp*yoffset+xoffset,
viewwidth*vid.bpp, viewheight, vid.width*vid.bpp, vid.width);
}
else if (type == postimg_flip) // Flip the screen upside-down
{
UINT8 *tmpscr = screens[4];
UINT8 *srcscr = screens[0];
INT32 y, y2;
for (y = yoffset, y2 = yoffset+viewheight - 1; y < yoffset+viewheight; y++, y2--)
M_Memcpy(&tmpscr[(y2*vid.width)+xoffset], &srcscr[(y*vid.width)+xoffset], viewwidth);
VID_BlitLinearScreen(tmpscr+vid.width*vid.bpp*yoffset+xoffset, screens[0]+vid.width*vid.bpp*yoffset+xoffset,
viewwidth*vid.bpp, viewheight, vid.width*vid.bpp, vid.width);
}
else if (type == postimg_heat) // Heat wave
{
UINT8 *tmpscr = screens[4];
UINT8 *srcscr = screens[0];
INT32 y;
// Make sure table is built
if (heatshifter == NULL || lastheight != viewheight)
{
if (heatshifter)
Z_Free(heatshifter);
heatshifter = static_cast<boolean*>(Z_Calloc(viewheight * sizeof(boolean), PU_STATIC, NULL));
for (y = 0; y < viewheight; y++)
{
if (M_RandomChance(FRACUNIT/8)) // 12.5%
heatshifter[y] = true;
}
heatindex[0] = heatindex[1] = heatindex[2] = heatindex[3] = 0;
lastheight = viewheight;
}
for (y = yoffset; y < yoffset+viewheight; y++)
{
if (heatshifter[heatindex[view]++])
{
// Shift this row of pixels to the right by 2
tmpscr[(y*vid.width)+xoffset] = srcscr[(y*vid.width)+xoffset];
M_Memcpy(&tmpscr[(y*vid.width)+xoffset], &srcscr[(y*vid.width)+xoffset+vid.dupx], viewwidth-vid.dupx);
}
else
M_Memcpy(&tmpscr[(y*vid.width)+xoffset], &srcscr[(y*vid.width)+xoffset], viewwidth);
heatindex[view] %= viewheight;
}
heatindex[view]++;
heatindex[view] %= vid.height;
VID_BlitLinearScreen(tmpscr+vid.width*vid.bpp*yoffset+xoffset, screens[0]+vid.width*vid.bpp*yoffset+xoffset,
viewwidth*vid.bpp, viewheight, vid.width*vid.bpp, vid.width);
}
else if (type == postimg_mirror) // Flip the screen on the x axis
{
UINT8 *tmpscr = screens[4];
UINT8 *srcscr = screens[0];
INT32 y, x, x2;
for (y = yoffset; y < yoffset+viewheight; y++)
{
for (x = xoffset, x2 = xoffset+((viewwidth*vid.bpp)-1); x < xoffset+(viewwidth*vid.bpp); x++, x2--)
tmpscr[y*vid.width + x2] = srcscr[y*vid.width + x];
}
VID_BlitLinearScreen(tmpscr+vid.width*vid.bpp*yoffset+xoffset, screens[0]+vid.width*vid.bpp*yoffset+xoffset,
viewwidth*vid.bpp, viewheight, vid.width*vid.bpp, vid.width);
}
#endif
}
// Generates a RGB565 color look-up table
void InitColorLUT(colorlookup_t *lut, RGBA_t *palette, boolean makecolors)
{

View file

@ -409,8 +409,6 @@ void V_DrawRightAlignedLSTitleHighString(INT32 x, INT32 y, INT32 option, const c
void V_DrawCenteredLSTitleLowString(INT32 x, INT32 y, INT32 option, const char *string);
void V_DrawRightAlignedLSTitleLowString(INT32 x, INT32 y, INT32 option, const char *string);
void V_DoPostProcessor(INT32 view, postimg_t type, INT32 param);
void V_DrawPatchFill(patch_t *pat);
void VID_BlitLinearScreen(const UINT8 *srcptr, UINT8 *destptr, INT32 width, INT32 height, size_t srcrowbytes,