mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-05-01 09:14:56 +00:00
feat(gameengine): TAA, mipmaps with anisotropic filtering, LOD bias
- postfx.taa: temporal anti-aliasing with Halton jitter, neighborhood clamping, Karis tonemap for stable history, configurable blend_factor from JSON - Texture loader: auto-generate full mipmap chain via SDL_GenerateMipmapsForGPUTexture - 16x anisotropic filtering on all textures - LOD bias 0.5 to reduce moire patterns on high-frequency textures at distance - TAA shader: 3x3 neighborhood clamp with expanded bbox to reduce flicker - Ping-pong history buffers for temporal accumulation - Sub-pixel jitter via Halton(2,3) sequence, 16-frame cycle Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -53,15 +53,21 @@ void WorkflowTextureLoadStep::Execute(const WorkflowStepDefinition& step, Workfl
|
||||
throw std::runtime_error("texture.load: GPU device not found in context");
|
||||
}
|
||||
|
||||
// Create GPU texture
|
||||
// Calculate mip levels: floor(log2(max(w,h))) + 1
|
||||
int maxDim = std::max(w, h);
|
||||
Uint32 numLevels = 1;
|
||||
while (maxDim > 1) { maxDim >>= 1; numLevels++; }
|
||||
|
||||
// Create GPU texture with mip chain
|
||||
SDL_GPUTextureCreateInfo tex_info = {};
|
||||
tex_info.type = SDL_GPU_TEXTURETYPE_2D;
|
||||
tex_info.format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
|
||||
tex_info.width = static_cast<Uint32>(w);
|
||||
tex_info.height = static_cast<Uint32>(h);
|
||||
tex_info.layer_count_or_depth = 1;
|
||||
tex_info.num_levels = 1;
|
||||
tex_info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER;
|
||||
tex_info.num_levels = numLevels;
|
||||
tex_info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER |
|
||||
(numLevels > 1 ? SDL_GPU_TEXTUREUSAGE_COLOR_TARGET : 0);
|
||||
|
||||
SDL_GPUTexture* texture = SDL_CreateGPUTexture(device, &tex_info);
|
||||
if (!texture) {
|
||||
@@ -105,6 +111,12 @@ void WorkflowTextureLoadStep::Execute(const WorkflowStepDefinition& step, Workfl
|
||||
|
||||
SDL_UploadToGPUTexture(copy_pass, &src, &dst, false);
|
||||
SDL_EndGPUCopyPass(copy_pass);
|
||||
|
||||
// Generate mipmaps from the uploaded base level
|
||||
if (numLevels > 1) {
|
||||
SDL_GenerateMipmapsForGPUTexture(cmd, texture);
|
||||
}
|
||||
|
||||
SDL_SubmitGPUCommandBuffer(cmd);
|
||||
SDL_ReleaseGPUTransferBuffer(device, transfer);
|
||||
|
||||
@@ -116,6 +128,11 @@ void WorkflowTextureLoadStep::Execute(const WorkflowStepDefinition& step, Workfl
|
||||
samp_info.address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_REPEAT;
|
||||
samp_info.address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_REPEAT;
|
||||
samp_info.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_REPEAT;
|
||||
samp_info.enable_anisotropy = true;
|
||||
samp_info.max_anisotropy = 16.0f;
|
||||
samp_info.mip_lod_bias = 0.5f; // bias toward higher mip = less aliasing at distance
|
||||
samp_info.min_lod = 0.0f;
|
||||
samp_info.max_lod = static_cast<float>(numLevels);
|
||||
|
||||
SDL_GPUSampler* sampler = SDL_CreateGPUSampler(device, &samp_info);
|
||||
if (!sampler) {
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
#include "services/interfaces/workflow/rendering/workflow_postfx_taa_step.hpp"
|
||||
#include "services/interfaces/workflow/workflow_step_parameter_resolver.hpp"
|
||||
|
||||
#include <SDL3/SDL_gpu.h>
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <cmath>
|
||||
|
||||
namespace sdl3cpp::services::impl {
|
||||
|
||||
// Halton sequence for sub-pixel jitter (low discrepancy, covers pixel well)
|
||||
static float halton(int index, int base) {
|
||||
float f = 1.0f;
|
||||
float r = 0.0f;
|
||||
int i = index;
|
||||
while (i > 0) {
|
||||
f /= static_cast<float>(base);
|
||||
r += f * (i % base);
|
||||
i /= base;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
WorkflowPostfxTaaStep::WorkflowPostfxTaaStep(std::shared_ptr<ILogger> logger)
|
||||
: logger_(std::move(logger)) {}
|
||||
|
||||
std::string WorkflowPostfxTaaStep::GetPluginId() const {
|
||||
return "postfx.taa";
|
||||
}
|
||||
|
||||
void WorkflowPostfxTaaStep::Execute(const WorkflowStepDefinition& step, WorkflowContext& context) {
|
||||
if (context.GetBool("frame_skip", false)) return;
|
||||
|
||||
auto* cmd = context.Get<SDL_GPUCommandBuffer*>("gpu_command_buffer", nullptr);
|
||||
auto* device = context.Get<SDL_GPUDevice*>("gpu_device", nullptr);
|
||||
auto* hdrTex = context.Get<SDL_GPUTexture*>("postfx_hdr_texture", nullptr);
|
||||
if (!cmd || !device || !hdrTex) return;
|
||||
|
||||
WorkflowStepParameterResolver params;
|
||||
auto getNum = [&](const char* name, float def) -> float {
|
||||
const auto* p = params.FindParameter(step, name);
|
||||
return (p && p->type == WorkflowParameterValue::Type::Number) ? static_cast<float>(p->numberValue) : def;
|
||||
};
|
||||
|
||||
float blendFactor = getNum("blend_factor", 0.05f);
|
||||
|
||||
uint32_t w = context.Get<uint32_t>("postfx_hdr_width", 0u);
|
||||
uint32_t h = context.Get<uint32_t>("postfx_hdr_height", 0u);
|
||||
if (w == 0 || h == 0) return;
|
||||
|
||||
// Track frame count for jitter sequence
|
||||
double frameCount = context.Get<double>("taa_frame_count", 0.0);
|
||||
frameCount += 1.0;
|
||||
context.Set<double>("taa_frame_count", frameCount);
|
||||
int frameIdx = static_cast<int>(frameCount);
|
||||
|
||||
// --- Apply jitter to projection matrix for NEXT frame ---
|
||||
// This ensures the current frame was rendered with jitter applied last frame
|
||||
auto projMatrix = context.Get<glm::mat4>("render.proj_matrix", glm::mat4(1.0f));
|
||||
float jitterX = (halton(frameIdx % 16 + 1, 2) - 0.5f) / static_cast<float>(w);
|
||||
float jitterY = (halton(frameIdx % 16 + 1, 3) - 0.5f) / static_cast<float>(h);
|
||||
glm::mat4 jitteredProj = projMatrix;
|
||||
jitteredProj[2][0] += jitterX * 2.0f;
|
||||
jitteredProj[2][1] += jitterY * 2.0f;
|
||||
context.Set<glm::mat4>("render.proj_matrix", jitteredProj);
|
||||
|
||||
// --- Create or get TAA pipeline ---
|
||||
auto* taaPipeline = context.Get<SDL_GPUGraphicsPipeline*>("postfx_taa_pipeline", nullptr);
|
||||
if (!taaPipeline) {
|
||||
// Need TAA shader - look for compiled shader in context
|
||||
auto* taaShader = context.Get<SDL_GPUShader*>("taa_fragment_shader", nullptr);
|
||||
auto* fullscreenVert = context.Get<SDL_GPUShader*>("postfx_vertex_shader", nullptr);
|
||||
if (!taaShader || !fullscreenVert) {
|
||||
// Shaders not compiled yet - skip TAA this frame
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_GPUGraphicsPipelineCreateInfo pipelineInfo = {};
|
||||
pipelineInfo.vertex_shader = fullscreenVert;
|
||||
pipelineInfo.fragment_shader = taaShader;
|
||||
pipelineInfo.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
|
||||
|
||||
SDL_GPUColorTargetDescription colorDesc = {};
|
||||
colorDesc.format = SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT;
|
||||
pipelineInfo.target_info.num_color_targets = 1;
|
||||
pipelineInfo.target_info.color_target_descriptions = &colorDesc;
|
||||
|
||||
taaPipeline = SDL_CreateGPUGraphicsPipeline(device, &pipelineInfo);
|
||||
if (taaPipeline) {
|
||||
context.Set<SDL_GPUGraphicsPipeline*>("postfx_taa_pipeline", taaPipeline);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Create or get history textures (ping-pong) ---
|
||||
auto* historyA = context.Get<SDL_GPUTexture*>("taa_history_a", nullptr);
|
||||
auto* historyB = context.Get<SDL_GPUTexture*>("taa_history_b", nullptr);
|
||||
auto taaW = context.Get<uint32_t>("taa_width", 0u);
|
||||
auto taaH = context.Get<uint32_t>("taa_height", 0u);
|
||||
|
||||
if (!historyA || !historyB || taaW != w || taaH != h) {
|
||||
if (historyA) SDL_ReleaseGPUTexture(device, historyA);
|
||||
if (historyB) SDL_ReleaseGPUTexture(device, historyB);
|
||||
|
||||
SDL_GPUTextureCreateInfo texInfo = {};
|
||||
texInfo.type = SDL_GPU_TEXTURETYPE_2D;
|
||||
texInfo.format = SDL_GPU_TEXTUREFORMAT_R16G16B16A16_FLOAT;
|
||||
texInfo.width = w;
|
||||
texInfo.height = h;
|
||||
texInfo.layer_count_or_depth = 1;
|
||||
texInfo.num_levels = 1;
|
||||
texInfo.usage = SDL_GPU_TEXTUREUSAGE_COLOR_TARGET | SDL_GPU_TEXTUREUSAGE_SAMPLER;
|
||||
|
||||
historyA = SDL_CreateGPUTexture(device, &texInfo);
|
||||
historyB = SDL_CreateGPUTexture(device, &texInfo);
|
||||
context.Set<SDL_GPUTexture*>("taa_history_a", historyA);
|
||||
context.Set<SDL_GPUTexture*>("taa_history_b", historyB);
|
||||
context.Set<uint32_t>("taa_width", w);
|
||||
context.Set<uint32_t>("taa_height", h);
|
||||
context.Set<bool>("taa_ping", true);
|
||||
}
|
||||
|
||||
// Ping-pong: read from one history, write to the other
|
||||
bool ping = context.GetBool("taa_ping", true);
|
||||
SDL_GPUTexture* historyRead = ping ? historyA : historyB;
|
||||
SDL_GPUTexture* historyWrite = ping ? historyB : historyA;
|
||||
context.Set<bool>("taa_ping", !ping);
|
||||
|
||||
auto* sampler = context.Get<SDL_GPUSampler*>("postfx_linear_sampler", nullptr);
|
||||
if (!sampler) return;
|
||||
|
||||
// --- TAA resolve pass: current HDR + history → historyWrite ---
|
||||
SDL_GPUColorTargetInfo colorTarget = {};
|
||||
colorTarget.texture = historyWrite;
|
||||
colorTarget.load_op = SDL_GPU_LOADOP_DONT_CARE;
|
||||
colorTarget.store_op = SDL_GPU_STOREOP_STORE;
|
||||
|
||||
SDL_GPURenderPass* pass = SDL_BeginGPURenderPass(cmd, &colorTarget, 1, nullptr);
|
||||
if (!pass) return;
|
||||
|
||||
SDL_BindGPUGraphicsPipeline(pass, taaPipeline);
|
||||
|
||||
SDL_GPUTextureSamplerBinding bindings[2] = {};
|
||||
bindings[0].texture = hdrTex;
|
||||
bindings[0].sampler = sampler;
|
||||
bindings[1].texture = historyRead;
|
||||
bindings[1].sampler = sampler;
|
||||
SDL_BindGPUFragmentSamplers(pass, 0, bindings, 2);
|
||||
|
||||
struct { float params[4]; } uniforms;
|
||||
uniforms.params[0] = blendFactor;
|
||||
uniforms.params[1] = 1.0f / static_cast<float>(w);
|
||||
uniforms.params[2] = 1.0f / static_cast<float>(h);
|
||||
uniforms.params[3] = static_cast<float>(frameCount);
|
||||
SDL_PushGPUFragmentUniformData(cmd, 0, &uniforms, sizeof(uniforms));
|
||||
|
||||
SDL_DrawGPUPrimitives(pass, 3, 1, 0, 0);
|
||||
SDL_EndGPURenderPass(pass);
|
||||
|
||||
// Replace the HDR texture with the TAA result for downstream post-FX
|
||||
context.Set<SDL_GPUTexture*>("postfx_hdr_texture", historyWrite);
|
||||
}
|
||||
|
||||
} // namespace sdl3cpp::services::impl
|
||||
@@ -43,6 +43,7 @@
|
||||
#include "services/interfaces/workflow/rendering/workflow_geometry_create_flashlight_step.hpp"
|
||||
#include "services/interfaces/workflow/rendering/workflow_map_load_step.hpp"
|
||||
#include "services/interfaces/workflow/rendering/workflow_draw_map_step.hpp"
|
||||
#include "services/interfaces/workflow/rendering/workflow_postfx_taa_step.hpp"
|
||||
#include "services/interfaces/workflow/rendering/workflow_draw_textured_box_step.hpp"
|
||||
#include "services/interfaces/workflow/rendering/workflow_shadow_setup_step.hpp"
|
||||
#include "services/interfaces/workflow/rendering/workflow_shadow_pass_step.hpp"
|
||||
@@ -280,6 +281,7 @@ void WorkflowRegistrar::RegisterSteps(std::shared_ptr<IWorkflowStepRegistry> reg
|
||||
registry->RegisterStep(std::make_shared<WorkflowGeometryCreateFlashlightStep>(logger_));
|
||||
registry->RegisterStep(std::make_shared<WorkflowMapLoadStep>(logger_));
|
||||
registry->RegisterStep(std::make_shared<WorkflowDrawMapStep>(logger_));
|
||||
registry->RegisterStep(std::make_shared<WorkflowPostfxTaaStep>(logger_));
|
||||
registry->RegisterStep(std::make_shared<WorkflowShadowSetupStep>(logger_));
|
||||
registry->RegisterStep(std::make_shared<WorkflowShadowPassStep>(logger_));
|
||||
registry->RegisterStep(std::make_shared<WorkflowRenderPrepareStep>(logger_));
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "services/interfaces/i_workflow_step.hpp"
|
||||
#include "services/interfaces/i_logger.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace sdl3cpp::services::impl {
|
||||
|
||||
class WorkflowPostfxTaaStep : public IWorkflowStep {
|
||||
public:
|
||||
explicit WorkflowPostfxTaaStep(std::shared_ptr<ILogger> logger);
|
||||
std::string GetPluginId() const override;
|
||||
void Execute(const WorkflowStepDefinition& step, WorkflowContext& context) override;
|
||||
private:
|
||||
std::shared_ptr<ILogger> logger_;
|
||||
};
|
||||
|
||||
} // namespace sdl3cpp::services::impl
|
||||
Reference in New Issue
Block a user