mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-05-06 19:49:36 +00:00
Add Quake 3 menu and weapon workflow steps
This commit is contained in:
@@ -283,6 +283,9 @@ if(BUILD_SDL3_APP)
|
||||
src/services/impl/workflow/geometry/workflow_geometry_create_cube_step.cpp
|
||||
src/services/impl/workflow/geometry/workflow_geometry_create_plane_step.cpp
|
||||
src/services/impl/workflow/geometry/workflow_geometry_cube_generate_step.cpp
|
||||
src/services/impl/workflow/quake3/workflow_q3_menu_update_step.cpp
|
||||
src/services/impl/workflow/quake3/workflow_q3_overlay_draw_step.cpp
|
||||
src/services/impl/workflow/quake3/workflow_q3_weapon_update_step.cpp
|
||||
src/services/impl/workflow/rendering/workflow_bsp_build_collision_step.cpp
|
||||
src/services/impl/workflow/rendering/workflow_bsp_build_geometry_step.cpp
|
||||
src/services/impl/workflow/rendering/workflow_bsp_entity_update_step.cpp
|
||||
@@ -290,6 +293,7 @@ if(BUILD_SDL3_APP)
|
||||
src/services/impl/workflow/rendering/workflow_bsp_lightmap_atlas_step.cpp
|
||||
src/services/impl/workflow/rendering/workflow_bsp_load_step.cpp
|
||||
src/services/impl/workflow/rendering/workflow_bsp_parse_spawn_step.cpp
|
||||
src/services/impl/workflow/rendering/workflow_bsp_portal_view_step.cpp
|
||||
src/services/impl/workflow/rendering/workflow_bsp_upload_geometry_step.cpp
|
||||
src/services/impl/workflow/rendering/workflow_draw_map_step.cpp
|
||||
src/services/impl/workflow/rendering/workflow_draw_textured_box_step.cpp
|
||||
|
||||
@@ -28,13 +28,13 @@ fragment float4 main0(
|
||||
sampler shadowSampler [[sampler(1)]],
|
||||
texture2d<float> lightmapTex [[texture(2)]],
|
||||
sampler lightmapSampler [[sampler(2)]],
|
||||
texture2d<float> portalTex [[texture(3)]],
|
||||
sampler portalSampler [[sampler(3)]],
|
||||
constant PBRUniforms& pbr [[buffer(0)]])
|
||||
{
|
||||
(void)shadowMap;
|
||||
(void)shadowSampler;
|
||||
(void)in.worldNormal;
|
||||
(void)in.worldPos;
|
||||
(void)in.cameraPos;
|
||||
|
||||
float3 albedo = albedoTex.sample(albedoSampler, in.uv).rgb;
|
||||
float3 lightmap = lightmapTex.sample(lightmapSampler, in.lightmapUv).rgb;
|
||||
@@ -42,5 +42,32 @@ fragment float4 main0(
|
||||
float3 ambient = pbr.u_ambient.rgb * albedo;
|
||||
float exposure = (pbr.u_lightColor.a > 0.0) ? pbr.u_lightColor.a : 1.0;
|
||||
|
||||
if (pbr.u_material.w > 0.5) {
|
||||
float time = pbr.u_material.y;
|
||||
float3 viewDir = normalize(in.cameraPos - in.worldPos);
|
||||
float3 n = normalize(in.worldNormal);
|
||||
float3 reflected = reflect(-viewDir, n);
|
||||
float fresnel = pow(1.0 - saturate(dot(viewDir, n)), 3.0);
|
||||
|
||||
float2 reflectUv = reflected.xz * 0.32 + float2(0.5, 0.5);
|
||||
reflectUv += float2(sin(time * 1.7 + in.worldPos.y * 0.35),
|
||||
cos(time * 1.3 + in.worldPos.x * 0.28)) * 0.035;
|
||||
|
||||
float3 portalBase = albedoTex.sample(albedoSampler, reflectUv).rgb;
|
||||
float2 portalUv = fract(in.uv);
|
||||
portalUv.y = 1.0 - portalUv.y;
|
||||
float2 centered = portalUv - float2(0.5, 0.5);
|
||||
float radial = length(centered);
|
||||
float centerMask = 1.0 - smoothstep(0.34, 0.49, radial);
|
||||
float3 destination = portalTex.sample(portalSampler, portalUv).rgb;
|
||||
float pulse = 0.5 + 0.5 * sin(time * 3.0 + length(in.worldPos.xz) * 0.45);
|
||||
float3 glow = float3(0.18, 0.42, 0.95) * (0.35 + 0.35 * pulse);
|
||||
float3 reflectedColor = mix(portalBase, glow, 0.18 + 0.32 * fresnel);
|
||||
float3 color = mix(reflectedColor, destination, centerMask * 0.88);
|
||||
color += glow * (1.0 - centerMask) * 0.45;
|
||||
float alpha = mix(0.18 + 0.24 * fresnel, 0.92, centerMask);
|
||||
return float4((color * 1.08 + ambient * 0.08) * exposure, alpha);
|
||||
}
|
||||
|
||||
return float4((albedo * lightmap * overbright + ambient) * exposure, 1.0);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
#include <metal_stdlib>
|
||||
using namespace metal;
|
||||
|
||||
struct FragmentInput {
|
||||
float4 position [[position]];
|
||||
float2 uv;
|
||||
};
|
||||
|
||||
fragment float4 main0(FragmentInput in [[stage_in]],
|
||||
texture2d<float> overlayTex [[texture(0)]],
|
||||
sampler overlaySampler [[sampler(0)]]) {
|
||||
return overlayTex.sample(overlaySampler, in.uv);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#include <metal_stdlib>
|
||||
using namespace metal;
|
||||
|
||||
struct VertexInput {
|
||||
float3 position [[attribute(0)]];
|
||||
float2 uv [[attribute(1)]];
|
||||
};
|
||||
|
||||
struct VertexOutput {
|
||||
float4 position [[position]];
|
||||
float2 uv;
|
||||
};
|
||||
|
||||
vertex VertexOutput main0(VertexInput in [[stage_in]]) {
|
||||
VertexOutput out;
|
||||
out.position = float4(in.position, 1.0);
|
||||
out.uv = in.uv;
|
||||
return out;
|
||||
}
|
||||
@@ -8,6 +8,12 @@
|
||||
"typeVersion": 1,
|
||||
"position": [0, 0]
|
||||
},
|
||||
{
|
||||
"id": "q3_menu",
|
||||
"type": "q3.menu.update",
|
||||
"typeVersion": 1,
|
||||
"position": [100, 0]
|
||||
},
|
||||
{
|
||||
"id": "physics_move",
|
||||
"type": "physics.fps.move",
|
||||
@@ -58,12 +64,24 @@
|
||||
"far": 500.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "q3_weapon",
|
||||
"type": "q3.weapon.update",
|
||||
"typeVersion": 1,
|
||||
"position": [690, 0]
|
||||
},
|
||||
{
|
||||
"id": "render_prepare",
|
||||
"type": "render.prepare",
|
||||
"typeVersion": 1,
|
||||
"position": [750, 0]
|
||||
},
|
||||
{
|
||||
"id": "portal_view",
|
||||
"type": "bsp.portal_view",
|
||||
"typeVersion": 1,
|
||||
"position": [775, 0]
|
||||
},
|
||||
{
|
||||
"id": "frame_begin",
|
||||
"type": "frame.gpu.begin",
|
||||
@@ -99,6 +117,12 @@
|
||||
"typeVersion": 1,
|
||||
"position": [1225, 0]
|
||||
},
|
||||
{
|
||||
"id": "q3_overlay",
|
||||
"type": "q3.overlay.draw",
|
||||
"typeVersion": 1,
|
||||
"position": [1235, 0]
|
||||
},
|
||||
{
|
||||
"id": "postfx_taa",
|
||||
"type": "postfx.taa",
|
||||
@@ -132,17 +156,21 @@
|
||||
}
|
||||
],
|
||||
"connections": {
|
||||
"input_poll": { "main": { "0": [{ "node": "physics_move", "type": "main", "index": 0 }] } },
|
||||
"input_poll": { "main": { "0": [{ "node": "q3_menu", "type": "main", "index": 0 }] } },
|
||||
"q3_menu": { "main": { "0": [{ "node": "physics_move", "type": "main", "index": 0 }] } },
|
||||
"physics_move": { "main": { "0": [{ "node": "physics_step", "type": "main", "index": 0 }] } },
|
||||
"physics_step": { "main": { "0": [{ "node": "sync_transforms", "type": "main", "index": 0 }] } },
|
||||
"sync_transforms": { "main": { "0": [{ "node": "bsp_entities_update", "type": "main", "index": 0 }] } },
|
||||
"bsp_entities_update": { "main": { "0": [{ "node": "camera_update", "type": "main", "index": 0 }] } },
|
||||
"camera_update": { "main": { "0": [{ "node": "render_prepare", "type": "main", "index": 0 }] } },
|
||||
"render_prepare": { "main": { "0": [{ "node": "frame_begin", "type": "main", "index": 0 }] } },
|
||||
"camera_update": { "main": { "0": [{ "node": "q3_weapon", "type": "main", "index": 0 }] } },
|
||||
"q3_weapon": { "main": { "0": [{ "node": "render_prepare", "type": "main", "index": 0 }] } },
|
||||
"render_prepare": { "main": { "0": [{ "node": "portal_view", "type": "main", "index": 0 }] } },
|
||||
"portal_view": { "main": { "0": [{ "node": "frame_begin", "type": "main", "index": 0 }] } },
|
||||
"frame_begin": { "main": { "0": [{ "node": "draw_map", "type": "main", "index": 0 }] } },
|
||||
"draw_map": { "main": { "0": [{ "node": "end_scene", "type": "main", "index": 0 }] } },
|
||||
"end_scene": { "main": { "0": [{ "node": "overlay_fps", "type": "main", "index": 0 }] } },
|
||||
"overlay_fps": { "main": { "0": [{ "node": "postfx_taa", "type": "main", "index": 0 }] } },
|
||||
"overlay_fps": { "main": { "0": [{ "node": "q3_overlay", "type": "main", "index": 0 }] } },
|
||||
"q3_overlay": { "main": { "0": [{ "node": "postfx_taa", "type": "main", "index": 0 }] } },
|
||||
"postfx_taa": { "main": { "0": [{ "node": "postfx_ssao", "type": "main", "index": 0 }] } },
|
||||
"postfx_ssao": { "main": { "0": [{ "node": "bloom_extract", "type": "main", "index": 0 }] } },
|
||||
"bloom_extract": { "main": { "0": [{ "node": "bloom_blur", "type": "main", "index": 0 }] } },
|
||||
|
||||
@@ -37,15 +37,15 @@
|
||||
"parameters": { "stage": "vertex", "output_key": "bsp_vertex_shader", "num_uniform_buffers": 1, "num_samplers": 0 },
|
||||
"inputs": { "shader_path": "shader_bsp_vert_path" } },
|
||||
{ "id": "compile_bsp_frag", "type": "graphics.gpu.shader.compile", "typeVersion": 1, "position": [1400, 0],
|
||||
"parameters": { "stage": "fragment", "output_key": "bsp_fragment_shader", "num_uniform_buffers": 1, "num_samplers": 3 },
|
||||
"parameters": { "stage": "fragment", "output_key": "bsp_fragment_shader", "num_uniform_buffers": 1, "num_samplers": 4 },
|
||||
"inputs": { "shader_path": "shader_bsp_frag_path" } },
|
||||
{ "id": "create_bsp_pipeline", "type": "graphics.gpu.pipeline.create", "typeVersion": 1, "position": [1500, 0],
|
||||
"parameters": { "vertex_shader_key": "bsp_vertex_shader", "fragment_shader_key": "bsp_fragment_shader", "vertex_format": "position_uv_lmuv_normal", "pipeline_key": "gpu_pipeline_bsp" } },
|
||||
"parameters": { "vertex_shader_key": "bsp_vertex_shader", "fragment_shader_key": "bsp_fragment_shader", "vertex_format": "position_uv_lmuv_normal", "pipeline_key": "gpu_pipeline_bsp", "alpha_blend": 1 } },
|
||||
{ "id": "tex_walls", "name": "Load Texture", "type": "texture.load", "typeVersion": 1, "position": [1600, 0],
|
||||
"parameters": { "inputs": { "image_path": "tex_walls_path" }, "outputs": { "texture": "walls_texture" } } },
|
||||
{ "id": "physics_world", "type": "physics.world.create", "typeVersion": 1, "position": [0, 200] },
|
||||
{ "id": "load_bsp", "name": "Load Q3 BSP", "type": "bsp.load", "typeVersion": 1, "position": [200, 200],
|
||||
"parameters": { "pk3_path": "${env:QUAKE3_PAK0}", "map_name": "q3dm7", "scale": 0.03125 } },
|
||||
"parameters": { "pk3_path": "${env:QUAKE3_PAK0}", "map_name": "${env:QUAKE3_MAP}", "scale": 0.03125 } },
|
||||
{ "id": "bsp_lightmap", "name": "BSP Lightmap Atlas", "type": "bsp.lightmap_atlas", "typeVersion": 1, "position": [400, 200] },
|
||||
{ "id": "bsp_geometry", "name": "BSP Build Geometry", "type": "bsp.build_geometry", "typeVersion": 1, "position": [600, 200],
|
||||
"parameters": { "patch_tess_level": 4 } },
|
||||
|
||||
@@ -42,6 +42,7 @@ void WorkflowGpuPipelineCreateStep::Execute(const WorkflowStepDefinition& step,
|
||||
const bool release_shaders = static_cast<int>(getNum("release_shaders", 1)) != 0;
|
||||
const std::string color_format_str = getStr("color_format", "swapchain");
|
||||
const bool has_depth = static_cast<int>(getNum("has_depth", 1)) != 0;
|
||||
const bool alpha_blend = static_cast<int>(getNum("alpha_blend", 0)) != 0;
|
||||
|
||||
// Get GPU device
|
||||
SDL_GPUDevice* device = context.Get<SDL_GPUDevice*>("gpu_device", nullptr);
|
||||
@@ -155,6 +156,16 @@ void WorkflowGpuPipelineCreateStep::Execute(const WorkflowStepDefinition& step,
|
||||
color_target.format = SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM;
|
||||
}
|
||||
}
|
||||
|
||||
if (alpha_blend) {
|
||||
color_target.blend_state.enable_blend = true;
|
||||
color_target.blend_state.src_color_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA;
|
||||
color_target.blend_state.dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
|
||||
color_target.blend_state.color_blend_op = SDL_GPU_BLENDOP_ADD;
|
||||
color_target.blend_state.src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE;
|
||||
color_target.blend_state.dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ZERO;
|
||||
color_target.blend_state.alpha_blend_op = SDL_GPU_BLENDOP_ADD;
|
||||
}
|
||||
}
|
||||
|
||||
// Build pipeline create info
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
#include "services/interfaces/workflow/quake3/workflow_q3_menu_update_step.hpp"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
|
||||
namespace sdl3cpp::services::impl {
|
||||
|
||||
WorkflowQ3MenuUpdateStep::WorkflowQ3MenuUpdateStep(std::shared_ptr<ILogger> logger)
|
||||
: logger_(std::move(logger)) {}
|
||||
|
||||
std::string WorkflowQ3MenuUpdateStep::GetPluginId() const {
|
||||
return "q3.menu.update";
|
||||
}
|
||||
|
||||
void WorkflowQ3MenuUpdateStep::Execute(const WorkflowStepDefinition& step, WorkflowContext& context) {
|
||||
bool open = context.GetBool("q3.menu_open", true);
|
||||
if (context.GetBool("input_key_escape_pressed", false)) {
|
||||
open = !open;
|
||||
}
|
||||
context.Set<bool>("q3.menu_open", open);
|
||||
|
||||
auto maps = context.Get<nlohmann::json>("q3.maps", nlohmann::json::array());
|
||||
if (!maps.is_array() || maps.empty()) {
|
||||
maps = nlohmann::json::array({"q3dm7"});
|
||||
}
|
||||
|
||||
int selected = context.Get<int>("q3.menu_selected_map", 0);
|
||||
selected = std::clamp(selected, 0, static_cast<int>(maps.size()) - 1);
|
||||
|
||||
if (open) {
|
||||
if (context.GetBool("input_key_up_pressed", false)) {
|
||||
selected = (selected + static_cast<int>(maps.size()) - 1) % static_cast<int>(maps.size());
|
||||
}
|
||||
if (context.GetBool("input_key_down_pressed", false)) {
|
||||
selected = (selected + 1) % static_cast<int>(maps.size());
|
||||
}
|
||||
if (context.GetBool("input_key_enter_pressed", false)) {
|
||||
const std::string map = maps[selected].get<std::string>();
|
||||
context.Set<std::string>("q3.pending_map", map);
|
||||
if (logger_) logger_->Info("q3.menu.update: selected map " + map + " (restart with QUAKE3_MAP=" + map + ")");
|
||||
}
|
||||
}
|
||||
|
||||
context.Set<int>("q3.menu_selected_map", selected);
|
||||
}
|
||||
|
||||
} // namespace sdl3cpp::services::impl
|
||||
@@ -0,0 +1,296 @@
|
||||
#include "services/interfaces/workflow/quake3/workflow_q3_overlay_draw_step.hpp"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace sdl3cpp::services::impl {
|
||||
|
||||
namespace {
|
||||
|
||||
std::vector<uint8_t> LoadBinary(const char* path) {
|
||||
std::ifstream f(path, std::ios::binary | std::ios::ate);
|
||||
if (!f.is_open()) return {};
|
||||
auto size = f.tellg();
|
||||
std::vector<uint8_t> data(static_cast<size_t>(size));
|
||||
f.seekg(0);
|
||||
f.read(reinterpret_cast<char*>(data.data()), size);
|
||||
return data;
|
||||
}
|
||||
|
||||
void Text(SDL_Renderer* r, float x, float y, const char* text, SDL_Color color) {
|
||||
SDL_SetRenderDrawColor(r, color.r, color.g, color.b, color.a);
|
||||
SDL_RenderDebugText(r, x, y, text);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
WorkflowQ3OverlayDrawStep::WorkflowQ3OverlayDrawStep(std::shared_ptr<ILogger> logger)
|
||||
: logger_(std::move(logger)) {}
|
||||
|
||||
WorkflowQ3OverlayDrawStep::~WorkflowQ3OverlayDrawStep() {
|
||||
if (renderer_) SDL_DestroyRenderer(renderer_);
|
||||
if (surface_) SDL_DestroySurface(surface_);
|
||||
if (device_) {
|
||||
if (sampler_) SDL_ReleaseGPUSampler(device_, sampler_);
|
||||
if (vtx_buf_) SDL_ReleaseGPUBuffer(device_, vtx_buf_);
|
||||
if (transfer_) SDL_ReleaseGPUTransferBuffer(device_, transfer_);
|
||||
if (tex_) SDL_ReleaseGPUTexture(device_, tex_);
|
||||
if (pipeline_) SDL_ReleaseGPUGraphicsPipeline(device_, pipeline_);
|
||||
}
|
||||
}
|
||||
|
||||
std::string WorkflowQ3OverlayDrawStep::GetPluginId() const {
|
||||
return "q3.overlay.draw";
|
||||
}
|
||||
|
||||
void WorkflowQ3OverlayDrawStep::TryInit(SDL_GPUDevice* device, SDL_Window* window) {
|
||||
if (disabled_ || ready_) return;
|
||||
device_ = device;
|
||||
|
||||
const char* driver = SDL_GetGPUDeviceDriver(device);
|
||||
const std::string driverName = driver ? driver : "";
|
||||
SDL_GPUShaderFormat shaderFormat = SDL_GPU_SHADERFORMAT_INVALID;
|
||||
std::vector<uint8_t> vert;
|
||||
std::vector<uint8_t> frag;
|
||||
const char* entry = "main";
|
||||
if (driverName == "metal") {
|
||||
shaderFormat = SDL_GPU_SHADERFORMAT_MSL;
|
||||
vert = LoadBinary("packages/quake3/shaders/msl/overlay.vert.metal");
|
||||
frag = LoadBinary("packages/quake3/shaders/msl/overlay.frag.metal");
|
||||
entry = "main0";
|
||||
} else if (driverName == "vulkan") {
|
||||
shaderFormat = SDL_GPU_SHADERFORMAT_SPIRV;
|
||||
vert = LoadBinary("packages/quake3/shaders/spirv/overlay.vert.spv");
|
||||
frag = LoadBinary("packages/quake3/shaders/spirv/overlay.frag.spv");
|
||||
} else {
|
||||
disabled_ = true;
|
||||
return;
|
||||
}
|
||||
if (vert.empty() || frag.empty()) {
|
||||
disabled_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_GPUShaderCreateInfo vsi = {};
|
||||
vsi.code = vert.data();
|
||||
vsi.code_size = vert.size();
|
||||
vsi.entrypoint = entry;
|
||||
vsi.format = shaderFormat;
|
||||
vsi.stage = SDL_GPU_SHADERSTAGE_VERTEX;
|
||||
SDL_GPUShaderCreateInfo fsi = {};
|
||||
fsi.code = frag.data();
|
||||
fsi.code_size = frag.size();
|
||||
fsi.entrypoint = entry;
|
||||
fsi.format = shaderFormat;
|
||||
fsi.stage = SDL_GPU_SHADERSTAGE_FRAGMENT;
|
||||
fsi.num_samplers = 1;
|
||||
auto* vs = SDL_CreateGPUShader(device, &vsi);
|
||||
auto* fs = SDL_CreateGPUShader(device, &fsi);
|
||||
if (!vs || !fs) {
|
||||
if (vs) SDL_ReleaseGPUShader(device, vs);
|
||||
if (fs) SDL_ReleaseGPUShader(device, fs);
|
||||
disabled_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_GPUVertexBufferDescription vbd = {};
|
||||
vbd.slot = 0;
|
||||
vbd.pitch = sizeof(float) * 5;
|
||||
vbd.input_rate = SDL_GPU_VERTEXINPUTRATE_VERTEX;
|
||||
SDL_GPUVertexAttribute attrs[2] = {};
|
||||
attrs[0] = {0, 0, SDL_GPU_VERTEXELEMENTFORMAT_FLOAT3, 0};
|
||||
attrs[1] = {1, 0, SDL_GPU_VERTEXELEMENTFORMAT_FLOAT2, sizeof(float) * 3};
|
||||
SDL_GPUVertexInputState vis = {};
|
||||
vis.vertex_buffer_descriptions = &vbd;
|
||||
vis.num_vertex_buffers = 1;
|
||||
vis.vertex_attributes = attrs;
|
||||
vis.num_vertex_attributes = 2;
|
||||
|
||||
SDL_GPUColorTargetDescription ctd = {};
|
||||
ctd.format = window ? SDL_GetGPUSwapchainTextureFormat(device, window) : SDL_GPU_TEXTUREFORMAT_B8G8R8A8_UNORM;
|
||||
ctd.blend_state.enable_blend = true;
|
||||
ctd.blend_state.src_color_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA;
|
||||
ctd.blend_state.dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
|
||||
ctd.blend_state.color_blend_op = SDL_GPU_BLENDOP_ADD;
|
||||
ctd.blend_state.src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE;
|
||||
ctd.blend_state.dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ZERO;
|
||||
ctd.blend_state.alpha_blend_op = SDL_GPU_BLENDOP_ADD;
|
||||
|
||||
SDL_GPUGraphicsPipelineCreateInfo pci = {};
|
||||
pci.vertex_shader = vs;
|
||||
pci.fragment_shader = fs;
|
||||
pci.vertex_input_state = vis;
|
||||
pci.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
|
||||
pci.rasterizer_state.fill_mode = SDL_GPU_FILLMODE_FILL;
|
||||
pci.rasterizer_state.cull_mode = SDL_GPU_CULLMODE_NONE;
|
||||
pci.rasterizer_state.front_face = SDL_GPU_FRONTFACE_COUNTER_CLOCKWISE;
|
||||
pci.depth_stencil_state.enable_depth_test = false;
|
||||
pci.depth_stencil_state.enable_depth_write = false;
|
||||
pci.target_info.num_color_targets = 1;
|
||||
pci.target_info.color_target_descriptions = &ctd;
|
||||
pipeline_ = SDL_CreateGPUGraphicsPipeline(device, &pci);
|
||||
SDL_ReleaseGPUShader(device, vs);
|
||||
SDL_ReleaseGPUShader(device, fs);
|
||||
if (!pipeline_) {
|
||||
disabled_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_GPUTextureCreateInfo tci = {};
|
||||
tci.type = SDL_GPU_TEXTURETYPE_2D;
|
||||
tci.format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
|
||||
tci.width = kW;
|
||||
tci.height = kH;
|
||||
tci.layer_count_or_depth = 1;
|
||||
tci.num_levels = 1;
|
||||
tci.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER;
|
||||
tex_ = SDL_CreateGPUTexture(device, &tci);
|
||||
SDL_GPUTransferBufferCreateInfo tbci = {};
|
||||
tbci.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
|
||||
tbci.size = kW * kH * 4;
|
||||
transfer_ = SDL_CreateGPUTransferBuffer(device, &tbci);
|
||||
SDL_GPUBufferCreateInfo bci = {};
|
||||
bci.usage = SDL_GPU_BUFFERUSAGE_VERTEX;
|
||||
bci.size = 6u * 5u * static_cast<uint32_t>(sizeof(float));
|
||||
vtx_buf_ = SDL_CreateGPUBuffer(device, &bci);
|
||||
SDL_GPUSamplerCreateInfo sci = {};
|
||||
sci.min_filter = SDL_GPU_FILTER_NEAREST;
|
||||
sci.mag_filter = SDL_GPU_FILTER_NEAREST;
|
||||
sci.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST;
|
||||
sci.address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
|
||||
sci.address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
|
||||
sampler_ = SDL_CreateGPUSampler(device, &sci);
|
||||
surface_ = SDL_CreateSurface(kW, kH, SDL_PIXELFORMAT_RGBA32);
|
||||
renderer_ = surface_ ? SDL_CreateSoftwareRenderer(surface_) : nullptr;
|
||||
ready_ = tex_ && transfer_ && vtx_buf_ && sampler_ && surface_ && renderer_;
|
||||
}
|
||||
|
||||
void WorkflowQ3OverlayDrawStep::DrawSurface(WorkflowContext& context, uint32_t frameW, uint32_t frameH) {
|
||||
SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 0);
|
||||
SDL_RenderClear(renderer_);
|
||||
|
||||
SDL_SetRenderDrawBlendMode(renderer_, SDL_BLENDMODE_BLEND);
|
||||
SDL_FRect hudBg{12, static_cast<float>(kH - 44), 230, 28};
|
||||
SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 150);
|
||||
SDL_RenderFillRect(renderer_, &hudBg);
|
||||
|
||||
const std::string weapon = context.Get<std::string>("q3.current_weapon", "weapon_machinegun");
|
||||
const int shots = context.Get<int>("q3.shots_fired", 0);
|
||||
std::string hud = "WEAPON " + weapon.substr(7) + " SHOTS " + std::to_string(shots);
|
||||
Text(renderer_, 20, static_cast<float>(kH - 36), hud.c_str(), SDL_Color{255, 216, 64, 255});
|
||||
Text(renderer_, static_cast<float>(kW / 2 - 4), static_cast<float>(kH / 2 - 4), "+", SDL_Color{255, 255, 255, 220});
|
||||
|
||||
if (context.GetBool("q3.menu_open", false)) {
|
||||
SDL_FRect panel{120, 42, 400, 250};
|
||||
SDL_SetRenderDrawColor(renderer_, 0, 0, 0, 210);
|
||||
SDL_RenderFillRect(renderer_, &panel);
|
||||
SDL_SetRenderDrawColor(renderer_, 40, 120, 220, 255);
|
||||
SDL_RenderRect(renderer_, &panel);
|
||||
Text(renderer_, 170, 62, "QUAKE III ARENA", SDL_Color{255, 216, 64, 255});
|
||||
Text(renderer_, 160, 88, "SKIRMISH / MAP SELECTION", SDL_Color{180, 220, 255, 255});
|
||||
|
||||
auto maps = context.Get<nlohmann::json>("q3.maps", nlohmann::json::array());
|
||||
int selected = context.Get<int>("q3.menu_selected_map", 0);
|
||||
for (int i = 0; i < 8 && i < static_cast<int>(maps.size()); ++i) {
|
||||
int idx = (selected / 8) * 8 + i;
|
||||
if (idx >= static_cast<int>(maps.size())) break;
|
||||
std::string line = (idx == selected ? "> " : " ") + maps[idx].get<std::string>();
|
||||
Text(renderer_, 176, 122 + i * 16, line.c_str(),
|
||||
idx == selected ? SDL_Color{255, 255, 255, 255} : SDL_Color{140, 190, 240, 255});
|
||||
}
|
||||
Text(renderer_, 154, 266, "UP/DOWN SELECT ENTER SET MAP ESC RESUME Q QUIT", SDL_Color{180, 180, 180, 255});
|
||||
auto pending = context.Get<std::string>("q3.pending_map", "");
|
||||
if (!pending.empty()) {
|
||||
std::string msg = "NEXT START: QUAKE3_MAP=" + pending;
|
||||
Text(renderer_, 160, 246, msg.c_str(), SDL_Color{255, 170, 80, 255});
|
||||
}
|
||||
}
|
||||
|
||||
SDL_RenderPresent(renderer_);
|
||||
}
|
||||
|
||||
void WorkflowQ3OverlayDrawStep::Render(SDL_GPUCommandBuffer* cmd, SDL_GPUTexture* swapchainTex,
|
||||
SDL_GPUDevice* device, uint32_t frameW, uint32_t frameH) {
|
||||
void* mapped = SDL_MapGPUTransferBuffer(device, transfer_, false);
|
||||
if (!mapped) return;
|
||||
std::memcpy(mapped, surface_->pixels, kW * kH * 4);
|
||||
SDL_UnmapGPUTransferBuffer(device, transfer_);
|
||||
|
||||
auto* copy = SDL_BeginGPUCopyPass(cmd);
|
||||
if (copy) {
|
||||
SDL_GPUTextureTransferInfo src = {};
|
||||
src.transfer_buffer = transfer_;
|
||||
src.pixels_per_row = kW;
|
||||
src.rows_per_layer = kH;
|
||||
SDL_GPUTextureRegion dst = {};
|
||||
dst.texture = tex_;
|
||||
dst.w = kW;
|
||||
dst.h = kH;
|
||||
dst.d = 1;
|
||||
SDL_UploadToGPUTexture(copy, &src, &dst, false);
|
||||
SDL_EndGPUCopyPass(copy);
|
||||
}
|
||||
|
||||
if (!vbuf_uploaded_) {
|
||||
const float verts[6][5] = {
|
||||
{-1, 1, 0, 0, 0}, { 1, 1, 0, 1, 0}, { 1, -1, 0, 1, 1},
|
||||
{-1, 1, 0, 0, 0}, { 1, -1, 0, 1, 1}, {-1, -1, 0, 0, 1},
|
||||
};
|
||||
const uint32_t size = sizeof(verts);
|
||||
SDL_GPUTransferBufferCreateInfo tb = {};
|
||||
tb.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
|
||||
tb.size = size;
|
||||
auto* tmp = SDL_CreateGPUTransferBuffer(device, &tb);
|
||||
if (tmp) {
|
||||
void* ptr = SDL_MapGPUTransferBuffer(device, tmp, false);
|
||||
if (ptr) {
|
||||
std::memcpy(ptr, verts, size);
|
||||
SDL_UnmapGPUTransferBuffer(device, tmp);
|
||||
}
|
||||
auto* cp = SDL_BeginGPUCopyPass(cmd);
|
||||
if (cp) {
|
||||
SDL_GPUTransferBufferLocation src = {tmp, 0};
|
||||
SDL_GPUBufferRegion dst = {vtx_buf_, 0, size};
|
||||
SDL_UploadToGPUBuffer(cp, &src, &dst, false);
|
||||
SDL_EndGPUCopyPass(cp);
|
||||
}
|
||||
SDL_ReleaseGPUTransferBuffer(device, tmp);
|
||||
}
|
||||
vbuf_uploaded_ = true;
|
||||
}
|
||||
|
||||
SDL_GPUColorTargetInfo target = {};
|
||||
target.texture = swapchainTex;
|
||||
target.load_op = SDL_GPU_LOADOP_LOAD;
|
||||
target.store_op = SDL_GPU_STOREOP_STORE;
|
||||
auto* pass = SDL_BeginGPURenderPass(cmd, &target, 1, nullptr);
|
||||
if (!pass) return;
|
||||
SDL_BindGPUGraphicsPipeline(pass, pipeline_);
|
||||
SDL_GPUBufferBinding vb = {vtx_buf_, 0};
|
||||
SDL_BindGPUVertexBuffers(pass, 0, &vb, 1);
|
||||
SDL_GPUTextureSamplerBinding ts = {tex_, sampler_};
|
||||
SDL_BindGPUFragmentSamplers(pass, 0, &ts, 1);
|
||||
SDL_DrawGPUPrimitives(pass, 6, 1, 0, 0);
|
||||
SDL_EndGPURenderPass(pass);
|
||||
}
|
||||
|
||||
void WorkflowQ3OverlayDrawStep::Execute(const WorkflowStepDefinition& step, WorkflowContext& context) {
|
||||
if (context.GetBool("frame_skip", false)) return;
|
||||
auto* cmd = context.Get<SDL_GPUCommandBuffer*>("gpu_command_buffer", nullptr);
|
||||
auto* swapchain = context.Get<SDL_GPUTexture*>("gpu_swapchain_texture", nullptr);
|
||||
auto* device = context.Get<SDL_GPUDevice*>("gpu_device", nullptr);
|
||||
if (!cmd || !swapchain || !device) return;
|
||||
if (!ready_) TryInit(device, context.Get<SDL_Window*>("sdl_window", nullptr));
|
||||
if (!ready_) return;
|
||||
const auto fw = context.Get<uint32_t>("frame_width", 1280u);
|
||||
const auto fh = context.Get<uint32_t>("frame_height", 960u);
|
||||
DrawSurface(context, fw, fh);
|
||||
Render(cmd, swapchain, device, fw, fh);
|
||||
}
|
||||
|
||||
} // namespace sdl3cpp::services::impl
|
||||
@@ -0,0 +1,97 @@
|
||||
#include "services/interfaces/workflow/quake3/workflow_q3_weapon_update_step.hpp"
|
||||
|
||||
#include <btBulletDynamicsCommon.h>
|
||||
#include <glm/glm.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <array>
|
||||
#include <string>
|
||||
|
||||
namespace sdl3cpp::services::impl {
|
||||
|
||||
namespace {
|
||||
|
||||
const std::array<const char*, 9> kWeapons = {
|
||||
"weapon_gauntlet",
|
||||
"weapon_machinegun",
|
||||
"weapon_shotgun",
|
||||
"weapon_grenadelauncher",
|
||||
"weapon_rocketlauncher",
|
||||
"weapon_lightning",
|
||||
"weapon_railgun",
|
||||
"weapon_plasmagun",
|
||||
"weapon_bfg"
|
||||
};
|
||||
|
||||
bool HasWeapon(const nlohmann::json& inventory, const std::string& weapon) {
|
||||
return weapon == "weapon_gauntlet" ||
|
||||
weapon == "weapon_machinegun" ||
|
||||
inventory.value(weapon, false);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
WorkflowQ3WeaponUpdateStep::WorkflowQ3WeaponUpdateStep(std::shared_ptr<ILogger> logger)
|
||||
: logger_(std::move(logger)) {}
|
||||
|
||||
std::string WorkflowQ3WeaponUpdateStep::GetPluginId() const {
|
||||
return "q3.weapon.update";
|
||||
}
|
||||
|
||||
void WorkflowQ3WeaponUpdateStep::Execute(const WorkflowStepDefinition& step, WorkflowContext& context) {
|
||||
auto inventory = context.Get<nlohmann::json>("q3.inventory", nlohmann::json::object());
|
||||
inventory["weapon_gauntlet"] = true;
|
||||
inventory["weapon_machinegun"] = true;
|
||||
|
||||
std::string current = context.Get<std::string>("q3.current_weapon", "weapon_machinegun");
|
||||
for (size_t i = 0; i < kWeapons.size(); ++i) {
|
||||
if (!context.GetBool("input_key_" + std::to_string(i + 1), false)) continue;
|
||||
const std::string requested = kWeapons[i];
|
||||
if (HasWeapon(inventory, requested)) {
|
||||
current = requested;
|
||||
context.Set<std::string>("q3.current_weapon", current);
|
||||
}
|
||||
}
|
||||
|
||||
const bool fireHeld = context.GetBool("input_mouse_left", false);
|
||||
const bool firePressed = context.GetBool("input_mouse_left_pressed", false);
|
||||
const uint32_t frame = static_cast<uint32_t>(context.GetDouble("loop.iteration", 0.0));
|
||||
uint32_t lastFire = context.Get<uint32_t>("q3.weapon_last_fire_frame", 0u);
|
||||
const uint32_t interval = current == "weapon_machinegun" ? 8u :
|
||||
current == "weapon_lightning" ? 3u :
|
||||
current == "weapon_gauntlet" ? 18u :
|
||||
28u;
|
||||
|
||||
bool wantsFire = firePressed || (fireHeld && current == "weapon_machinegun") || (fireHeld && current == "weapon_lightning");
|
||||
if (!context.GetBool("q3.menu_open", false) && wantsFire && (lastFire == 0u || frame >= lastFire + interval)) {
|
||||
lastFire = frame == 0u ? 1u : frame;
|
||||
context.Set<uint32_t>("q3.weapon_last_fire_frame", lastFire);
|
||||
context.Set<uint32_t>("q3.weapon_flash_until_frame", lastFire + 4u);
|
||||
context.Set<int>("q3.shots_fired", context.Get<int>("q3.shots_fired", 0) + 1);
|
||||
|
||||
auto* world = context.Get<btDiscreteDynamicsWorld*>("physics_world", nullptr);
|
||||
auto cameraState = context.Get<nlohmann::json>("camera.state", nlohmann::json::object());
|
||||
if (world && cameraState.contains("position") && cameraState.contains("front")) {
|
||||
const auto pos = cameraState["position"];
|
||||
const auto front = cameraState["front"];
|
||||
btVector3 from(pos[0].get<float>(), pos[1].get<float>(), pos[2].get<float>());
|
||||
btVector3 dir(front[0].get<float>(), front[1].get<float>(), front[2].get<float>());
|
||||
btVector3 to = from + dir.normalized() * 120.0f;
|
||||
btCollisionWorld::ClosestRayResultCallback hit(from, to);
|
||||
world->rayTest(from, to, hit);
|
||||
context.Set<bool>("q3.last_shot_hit", hit.hasHit());
|
||||
if (hit.hasHit()) {
|
||||
context.Set("q3.last_shot_position", nlohmann::json::array({
|
||||
hit.m_hitPointWorld.x(), hit.m_hitPointWorld.y(), hit.m_hitPointWorld.z()
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
if (logger_) logger_->Info("q3.weapon.update: fired " + current);
|
||||
}
|
||||
|
||||
context.Set("q3.inventory", inventory);
|
||||
context.Set<std::string>("q3.current_weapon", current);
|
||||
}
|
||||
|
||||
} // namespace sdl3cpp::services::impl
|
||||
@@ -316,6 +316,7 @@ void WorkflowBspBuildGeometryStep::Execute(const WorkflowStepDefinition& step, W
|
||||
mapNodes.push_back({
|
||||
{"name", "bsp_" + map_name},
|
||||
{"texture_index", texIdx},
|
||||
{"texture_name", (texIdx >= 0 && texIdx < numTextures) ? std::string(bspTextures[texIdx].name) : std::string{}},
|
||||
{"index_offset", indexOffset},
|
||||
{"index_count", group.indices.size()}
|
||||
});
|
||||
|
||||
+23
-7
@@ -28,19 +28,27 @@ bool ReadVec3(const nlohmann::json& value, btVector3& out) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool InBounds(const btVector3& p, const nlohmann::json& bounds, float pad) {
|
||||
bool ReadBounds(const nlohmann::json& bounds, btVector3& mn, btVector3& mx) {
|
||||
if (!bounds.is_object() || !bounds.contains("min") || !bounds.contains("max")) return false;
|
||||
btVector3 mn, mx;
|
||||
if (!ReadVec3(bounds["min"], mn) || !ReadVec3(bounds["max"], mx)) return false;
|
||||
return p.x() >= mn.x() - pad && p.x() <= mx.x() + pad &&
|
||||
p.y() >= mn.y() - pad && p.y() <= mx.y() + pad &&
|
||||
p.z() >= mn.z() - pad && p.z() <= mx.z() + pad;
|
||||
return ReadVec3(bounds["min"], mn) && ReadVec3(bounds["max"], mx);
|
||||
}
|
||||
|
||||
bool AabbIntersectsBounds(const btVector3& bodyMin,
|
||||
const btVector3& bodyMax,
|
||||
const nlohmann::json& bounds,
|
||||
float pad) {
|
||||
btVector3 triggerMin, triggerMax;
|
||||
if (!ReadBounds(bounds, triggerMin, triggerMax)) return false;
|
||||
return bodyMax.x() >= triggerMin.x() - pad && bodyMin.x() <= triggerMax.x() + pad &&
|
||||
bodyMax.y() >= triggerMin.y() - pad && bodyMin.y() <= triggerMax.y() + pad &&
|
||||
bodyMax.z() >= triggerMin.z() - pad && bodyMin.z() <= triggerMax.z() + pad;
|
||||
}
|
||||
|
||||
void TeleportBody(btRigidBody* body, const btVector3& dest) {
|
||||
btTransform xform;
|
||||
xform.setIdentity();
|
||||
xform.setOrigin(dest);
|
||||
body->setCenterOfMassTransform(xform);
|
||||
body->setWorldTransform(xform);
|
||||
if (body->getMotionState()) body->getMotionState()->setWorldTransform(xform);
|
||||
body->setLinearVelocity(btVector3(0, 0, 0));
|
||||
@@ -81,6 +89,9 @@ void WorkflowBspEntityUpdateStep::Execute(const WorkflowStepDefinition& step, Wo
|
||||
btTransform xform;
|
||||
body->getMotionState()->getWorldTransform(xform);
|
||||
const btVector3 playerPos = xform.getOrigin();
|
||||
btVector3 playerAabbMin;
|
||||
btVector3 playerAabbMax;
|
||||
body->getCollisionShape()->getAabb(xform, playerAabbMin, playerAabbMax);
|
||||
const uint32_t frame = static_cast<uint32_t>(context.GetDouble("loop.iteration", 0.0));
|
||||
|
||||
auto collected = context.Get<nlohmann::json>("q3.collected", nlohmann::json::object());
|
||||
@@ -111,7 +122,10 @@ void WorkflowBspEntityUpdateStep::Execute(const WorkflowStepDefinition& step, Wo
|
||||
}
|
||||
|
||||
if (classname != "trigger_push" && classname != "trigger_teleport") continue;
|
||||
if (!ent.contains("bounds") || !InBounds(playerPos, ent["bounds"], 0.15f)) continue;
|
||||
if (!ent.contains("bounds") ||
|
||||
!AabbIntersectsBounds(playerAabbMin, playerAabbMax, ent["bounds"], 0.15f)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const uint32_t lastFrame = cooldowns.value(id, 0u);
|
||||
const uint32_t cooldownFrames = classname == "trigger_teleport" ? 45u : 15u;
|
||||
@@ -124,6 +138,8 @@ void WorkflowBspEntityUpdateStep::Execute(const WorkflowStepDefinition& step, Wo
|
||||
if (classname == "trigger_teleport") {
|
||||
target += btVector3(0, 1.0f, 0);
|
||||
TeleportBody(body, target);
|
||||
playerAabbMin = target - btVector3(0.3f, 0.8f, 0.3f);
|
||||
playerAabbMax = target + btVector3(0.3f, 0.8f, 0.3f);
|
||||
if (logger_) logger_->Info("bsp.entities.update: teleported player via " + id);
|
||||
} else {
|
||||
body->setLinearVelocity(JumpPadVelocity(playerPos, target));
|
||||
|
||||
@@ -38,7 +38,8 @@ void WorkflowBspLoadStep::Execute(const WorkflowStepDefinition& step, WorkflowCo
|
||||
};
|
||||
|
||||
const std::string pk3_path = getStr("pk3_path", "");
|
||||
const std::string map_name = getStr("map_name", "q3dm17");
|
||||
std::string map_name = getStr("map_name", "q3dm17");
|
||||
if (map_name.empty()) map_name = "q3dm7";
|
||||
const float scale = getNum("scale", 1.0f / 32.0f);
|
||||
|
||||
if (pk3_path.empty()) {
|
||||
@@ -52,6 +53,18 @@ void WorkflowBspLoadStep::Execute(const WorkflowStepDefinition& step, WorkflowCo
|
||||
throw std::runtime_error("bsp.load: Failed to open pk3: " + pk3_path);
|
||||
}
|
||||
|
||||
nlohmann::json maps = nlohmann::json::array();
|
||||
const zip_int64_t entries = zip_get_num_entries(archive, 0);
|
||||
for (zip_uint64_t i = 0; i < static_cast<zip_uint64_t>(entries); ++i) {
|
||||
const char* name = zip_get_name(archive, i, 0);
|
||||
if (!name) continue;
|
||||
std::string entry(name);
|
||||
if (entry.rfind("maps/", 0) != 0 || entry.size() <= 9) continue;
|
||||
if (entry.substr(entry.size() - 4) != ".bsp") continue;
|
||||
maps.push_back(entry.substr(5, entry.size() - 9));
|
||||
}
|
||||
context.Set("q3.maps", maps);
|
||||
|
||||
std::string bsp_entry = "maps/" + map_name + ".bsp";
|
||||
zip_stat_t st;
|
||||
if (zip_stat(archive, bsp_entry.c_str(), 0, &st) != 0) {
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
#include "services/interfaces/workflow/rendering/workflow_bsp_portal_view_step.hpp"
|
||||
#include "services/interfaces/workflow/rendering/rendering_types.hpp"
|
||||
|
||||
#include <SDL3/SDL_gpu.h>
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace sdl3cpp::services::impl {
|
||||
|
||||
namespace {
|
||||
|
||||
bool ReadVec3(const nlohmann::json& value, glm::vec3& out) {
|
||||
if (!value.is_array() || value.size() != 3) return false;
|
||||
out = glm::vec3(value[0].get<float>(), value[1].get<float>(), value[2].get<float>());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FindPortalDestination(const nlohmann::json& entities, glm::vec3& out) {
|
||||
if (!entities.is_array()) return false;
|
||||
for (const auto& ent : entities) {
|
||||
if (ent.value("classname", std::string{}) != "trigger_teleport") continue;
|
||||
if (ent.contains("target_position") && ReadVec3(ent["target_position"], out)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
WorkflowBspPortalViewStep::WorkflowBspPortalViewStep(std::shared_ptr<ILogger> logger)
|
||||
: logger_(std::move(logger)) {}
|
||||
|
||||
std::string WorkflowBspPortalViewStep::GetPluginId() const {
|
||||
return "bsp.portal_view";
|
||||
}
|
||||
|
||||
void WorkflowBspPortalViewStep::Execute(const WorkflowStepDefinition& step, WorkflowContext& context) {
|
||||
if (context.GetBool("frame_skip", false)) return;
|
||||
|
||||
SDL_GPUDevice* device = context.Get<SDL_GPUDevice*>("gpu_device", nullptr);
|
||||
SDL_Window* window = context.Get<SDL_Window*>("sdl_window", nullptr);
|
||||
auto* pipeline = context.Get<SDL_GPUGraphicsPipeline*>("gpu_pipeline_bsp", nullptr);
|
||||
auto* lmTex = context.Get<SDL_GPUTexture*>("bsp_lightmap_atlas_gpu", nullptr);
|
||||
auto* lmSamp = context.Get<SDL_GPUSampler*>("bsp_lightmap_atlas_sampler", nullptr);
|
||||
const auto* mapNodes = context.TryGet<nlohmann::json>("map.nodes");
|
||||
const auto* entities = context.TryGet<nlohmann::json>("bsp.entities");
|
||||
|
||||
if (!device || !window || !pipeline || !lmTex || !lmSamp ||
|
||||
!mapNodes || !mapNodes->is_array() || mapNodes->empty() || !entities) {
|
||||
return;
|
||||
}
|
||||
|
||||
glm::vec3 dest;
|
||||
if (!FindPortalDestination(*entities, dest)) return;
|
||||
dest += glm::vec3(0.0f, 1.4f, 0.0f);
|
||||
|
||||
constexpr uint32_t kPortalSize = 512;
|
||||
auto* portalTex = context.Get<SDL_GPUTexture*>("bsp_portal_view_texture", nullptr);
|
||||
auto* portalDepth = context.Get<SDL_GPUTexture*>("bsp_portal_view_depth", nullptr);
|
||||
auto* portalSampler = context.Get<SDL_GPUSampler*>("bsp_portal_view_sampler", nullptr);
|
||||
|
||||
if (!portalTex) {
|
||||
SDL_GPUTextureCreateInfo ti = {};
|
||||
ti.type = SDL_GPU_TEXTURETYPE_2D;
|
||||
ti.format = SDL_GetGPUSwapchainTextureFormat(device, window);
|
||||
ti.width = kPortalSize;
|
||||
ti.height = kPortalSize;
|
||||
ti.layer_count_or_depth = 1;
|
||||
ti.num_levels = 1;
|
||||
ti.usage = SDL_GPU_TEXTUREUSAGE_COLOR_TARGET | SDL_GPU_TEXTUREUSAGE_SAMPLER;
|
||||
portalTex = SDL_CreateGPUTexture(device, &ti);
|
||||
if (!portalTex) return;
|
||||
context.Set<SDL_GPUTexture*>("bsp_portal_view_texture", portalTex);
|
||||
}
|
||||
|
||||
if (!portalDepth) {
|
||||
SDL_GPUTextureCreateInfo di = {};
|
||||
di.type = SDL_GPU_TEXTURETYPE_2D;
|
||||
di.format = SDL_GPU_TEXTUREFORMAT_D32_FLOAT;
|
||||
di.width = kPortalSize;
|
||||
di.height = kPortalSize;
|
||||
di.layer_count_or_depth = 1;
|
||||
di.num_levels = 1;
|
||||
di.usage = SDL_GPU_TEXTUREUSAGE_DEPTH_STENCIL_TARGET;
|
||||
portalDepth = SDL_CreateGPUTexture(device, &di);
|
||||
if (!portalDepth) return;
|
||||
context.Set<SDL_GPUTexture*>("bsp_portal_view_depth", portalDepth);
|
||||
}
|
||||
|
||||
if (!portalSampler) {
|
||||
SDL_GPUSamplerCreateInfo si = {};
|
||||
si.min_filter = SDL_GPU_FILTER_LINEAR;
|
||||
si.mag_filter = SDL_GPU_FILTER_LINEAR;
|
||||
si.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_LINEAR;
|
||||
si.address_mode_u = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
|
||||
si.address_mode_v = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
|
||||
si.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
|
||||
portalSampler = SDL_CreateGPUSampler(device, &si);
|
||||
if (!portalSampler) return;
|
||||
context.Set<SDL_GPUSampler*>("bsp_portal_view_sampler", portalSampler);
|
||||
}
|
||||
|
||||
SDL_GPUCommandBuffer* cmd = SDL_AcquireGPUCommandBuffer(device);
|
||||
if (!cmd) return;
|
||||
|
||||
SDL_GPUColorTargetInfo colorTarget = {};
|
||||
colorTarget.texture = portalTex;
|
||||
colorTarget.clear_color = {0.02f, 0.03f, 0.05f, 1.0f};
|
||||
colorTarget.load_op = SDL_GPU_LOADOP_CLEAR;
|
||||
colorTarget.store_op = SDL_GPU_STOREOP_STORE;
|
||||
|
||||
SDL_GPUDepthStencilTargetInfo depthTarget = {};
|
||||
depthTarget.texture = portalDepth;
|
||||
depthTarget.clear_depth = 1.0f;
|
||||
depthTarget.load_op = SDL_GPU_LOADOP_CLEAR;
|
||||
depthTarget.store_op = SDL_GPU_STOREOP_DONT_CARE;
|
||||
|
||||
SDL_GPURenderPass* pass = SDL_BeginGPURenderPass(cmd, &colorTarget, 1, &depthTarget);
|
||||
if (!pass) {
|
||||
SDL_CancelGPUCommandBuffer(cmd);
|
||||
return;
|
||||
}
|
||||
|
||||
const float yaw = context.Get<float>("camera_yaw", 0.0f);
|
||||
const float pitch = context.Get<float>("camera_pitch", 0.0f);
|
||||
glm::vec3 front;
|
||||
front.x = std::cos(pitch) * (-std::sin(yaw));
|
||||
front.y = std::sin(pitch);
|
||||
front.z = std::cos(pitch) * (-std::cos(yaw));
|
||||
front = glm::normalize(front);
|
||||
|
||||
glm::mat4 view = glm::lookAt(dest, dest + front, glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
glm::mat4 proj = glm::perspective(glm::radians(90.0f), 1.0f, 0.1f, 500.0f);
|
||||
glm::mat4 model = glm::mat4(1.0f);
|
||||
glm::mat4 mvp = proj * view * model;
|
||||
|
||||
rendering::VertexUniformData vu = {};
|
||||
std::memcpy(vu.mvp, glm::value_ptr(mvp), sizeof(float) * 16);
|
||||
std::memcpy(vu.model_mat, glm::value_ptr(model), sizeof(float) * 16);
|
||||
vu.normal[1] = 1.0f;
|
||||
vu.uv_scale[0] = 1.0f;
|
||||
vu.uv_scale[1] = 1.0f;
|
||||
vu.camera_pos[0] = dest.x;
|
||||
vu.camera_pos[1] = dest.y;
|
||||
vu.camera_pos[2] = dest.z;
|
||||
auto shadowVP = context.Get<glm::mat4>("render.shadow_vp", glm::mat4(1.0f));
|
||||
std::memcpy(vu.shadow_vp, glm::value_ptr(shadowVP), sizeof(float) * 16);
|
||||
|
||||
auto fu = context.Get<rendering::FragmentUniformData>("render.frag_uniforms", rendering::FragmentUniformData{});
|
||||
fu.material[0] = 0.7f;
|
||||
fu.material[1] = static_cast<float>(context.GetDouble("frame.elapsed", 0.0));
|
||||
fu.material[2] = 2.0f;
|
||||
fu.material[3] = 0.0f;
|
||||
|
||||
const auto& firstNode = (*mapNodes)[0];
|
||||
std::string meshName = firstNode["name"];
|
||||
auto* vb = context.Get<SDL_GPUBuffer*>("plane_" + meshName + "_vb", nullptr);
|
||||
auto* ib = context.Get<SDL_GPUBuffer*>("plane_" + meshName + "_ib", nullptr);
|
||||
if (!vb || !ib) {
|
||||
SDL_EndGPURenderPass(pass);
|
||||
SDL_SubmitGPUCommandBuffer(cmd);
|
||||
return;
|
||||
}
|
||||
|
||||
SDL_BindGPUGraphicsPipeline(pass, pipeline);
|
||||
SDL_GPUBufferBinding vbBind = {};
|
||||
vbBind.buffer = vb;
|
||||
SDL_BindGPUVertexBuffers(pass, 0, &vbBind, 1);
|
||||
SDL_GPUBufferBinding ibBind = {};
|
||||
ibBind.buffer = ib;
|
||||
SDL_BindGPUIndexBuffer(pass, &ibBind, SDL_GPU_INDEXELEMENTSIZE_32BIT);
|
||||
SDL_PushGPUVertexUniformData(cmd, 0, &vu, sizeof(vu));
|
||||
SDL_PushGPUFragmentUniformData(cmd, 0, &fu, sizeof(fu));
|
||||
|
||||
for (const auto& node : *mapNodes) {
|
||||
const int texIdx = node.value("texture_index", -1);
|
||||
SDL_GPUTexture* albedoTex = nullptr;
|
||||
SDL_GPUSampler* albedoSamp = nullptr;
|
||||
if (texIdx >= 0) {
|
||||
const std::string texKey = "bsp_tex_" + std::to_string(texIdx);
|
||||
albedoTex = context.Get<SDL_GPUTexture*>(texKey + "_gpu", nullptr);
|
||||
albedoSamp = context.Get<SDL_GPUSampler*>(texKey + "_sampler", nullptr);
|
||||
}
|
||||
if (!albedoTex || !albedoSamp) continue;
|
||||
|
||||
SDL_GPUTextureSamplerBinding bindings[4] = {};
|
||||
bindings[0].texture = albedoTex;
|
||||
bindings[0].sampler = albedoSamp;
|
||||
bindings[1].texture = albedoTex;
|
||||
bindings[1].sampler = albedoSamp;
|
||||
bindings[2].texture = lmTex;
|
||||
bindings[2].sampler = lmSamp;
|
||||
bindings[3].texture = albedoTex;
|
||||
bindings[3].sampler = albedoSamp;
|
||||
SDL_BindGPUFragmentSamplers(pass, 0, bindings, 4);
|
||||
|
||||
SDL_DrawGPUIndexedPrimitives(pass,
|
||||
node["index_count"].get<uint32_t>(),
|
||||
1,
|
||||
node.value("index_offset", 0u),
|
||||
0,
|
||||
0);
|
||||
}
|
||||
|
||||
SDL_EndGPURenderPass(pass);
|
||||
SDL_SubmitGPUCommandBuffer(cmd);
|
||||
}
|
||||
|
||||
} // namespace sdl3cpp::services::impl
|
||||
@@ -7,10 +7,29 @@
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
|
||||
namespace sdl3cpp::services::impl {
|
||||
|
||||
namespace {
|
||||
|
||||
std::string ToLower(std::string value) {
|
||||
std::transform(value.begin(), value.end(), value.begin(), [](unsigned char c) {
|
||||
return static_cast<char>(std::tolower(c));
|
||||
});
|
||||
return value;
|
||||
}
|
||||
|
||||
bool IsPortalTexture(const std::string& textureName) {
|
||||
const std::string lower = ToLower(textureName);
|
||||
return lower.find("portal_sfx") != std::string::npos ||
|
||||
lower.find("mapobjects/portal") != std::string::npos;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
WorkflowDrawMapStep::WorkflowDrawMapStep(std::shared_ptr<ILogger> logger)
|
||||
: logger_(std::move(logger)) {}
|
||||
|
||||
@@ -89,6 +108,8 @@ void WorkflowDrawMapStep::Execute(const WorkflowStepDefinition& step, WorkflowCo
|
||||
// BSP lightmap atlas (shared across all groups)
|
||||
auto* lm_tex = context.Get<SDL_GPUTexture*>("bsp_lightmap_atlas_gpu", nullptr);
|
||||
auto* lm_samp = context.Get<SDL_GPUSampler*>("bsp_lightmap_atlas_sampler", nullptr);
|
||||
auto* portal_tex = context.Get<SDL_GPUTexture*>("bsp_portal_view_texture", nullptr);
|
||||
auto* portal_samp = context.Get<SDL_GPUSampler*>("bsp_portal_view_sampler", nullptr);
|
||||
|
||||
if (isBsp) {
|
||||
// BSP mode: single VB + single IB, per-group draw calls
|
||||
@@ -104,18 +125,19 @@ void WorkflowDrawMapStep::Execute(const WorkflowStepDefinition& step, WorkflowCo
|
||||
SDL_GPUBufferBinding ibBind = {}; ibBind.buffer = ib;
|
||||
SDL_BindGPUIndexBuffer(pass, &ibBind, SDL_GPU_INDEXELEMENTSIZE_32BIT);
|
||||
|
||||
// Push uniforms once (same for all groups)
|
||||
// Vertex uniforms are shared by all BSP texture groups.
|
||||
SDL_PushGPUVertexUniformData(cmd, 0, &vu, sizeof(vu));
|
||||
SDL_PushGPUFragmentUniformData(cmd, 0, &fu, sizeof(fu));
|
||||
|
||||
// Default texture fallback
|
||||
auto* defaultTex = context.Get<SDL_GPUTexture*>(defaultTexture + "_gpu", nullptr);
|
||||
auto* defaultSamp = context.Get<SDL_GPUSampler*>(defaultTexture + "_sampler", nullptr);
|
||||
const float elapsed = static_cast<float>(context.GetDouble("frame.elapsed", 0.0));
|
||||
|
||||
for (const auto& node : *mapNodes) {
|
||||
uint32_t indexCount = node["index_count"];
|
||||
uint32_t indexOffset = node.value("index_offset", 0u);
|
||||
int texIdx = node.value("texture_index", -1);
|
||||
std::string textureName = node.value("texture_name", std::string{});
|
||||
|
||||
// Look up per-texture albedo
|
||||
SDL_GPUTexture* albedoTex = nullptr;
|
||||
@@ -133,16 +155,22 @@ void WorkflowDrawMapStep::Execute(const WorkflowStepDefinition& step, WorkflowCo
|
||||
}
|
||||
if (!albedoTex || !albedoSamp) continue;
|
||||
|
||||
// Bind 3 samplers: albedo, shadow, lightmap
|
||||
SDL_GPUTextureSamplerBinding bindings[3] = {};
|
||||
// Bind 4 samplers: albedo, shadow, lightmap, portal destination.
|
||||
SDL_GPUTextureSamplerBinding bindings[4] = {};
|
||||
bindings[0].texture = albedoTex;
|
||||
bindings[0].sampler = albedoSamp;
|
||||
bindings[1].texture = shadow_tex ? shadow_tex : albedoTex;
|
||||
bindings[1].sampler = shadow_samp ? shadow_samp : albedoSamp;
|
||||
bindings[2].texture = lm_tex ? lm_tex : albedoTex;
|
||||
bindings[2].sampler = lm_samp ? lm_samp : albedoSamp;
|
||||
SDL_BindGPUFragmentSamplers(pass, 0, bindings, 3);
|
||||
bindings[3].texture = portal_tex ? portal_tex : albedoTex;
|
||||
bindings[3].sampler = portal_samp ? portal_samp : albedoSamp;
|
||||
SDL_BindGPUFragmentSamplers(pass, 0, bindings, 4);
|
||||
|
||||
auto groupFu = fu;
|
||||
groupFu.material[1] = elapsed;
|
||||
groupFu.material[3] = IsPortalTexture(textureName) ? 1.0f : 0.0f;
|
||||
SDL_PushGPUFragmentUniformData(cmd, 0, &groupFu, sizeof(groupFu));
|
||||
SDL_DrawGPUIndexedPrimitives(pass, indexCount, 1, indexOffset, 0, 0);
|
||||
}
|
||||
} else {
|
||||
|
||||
+31
@@ -19,6 +19,11 @@ void WorkflowInputPollStep::Execute(
|
||||
const WorkflowStepDefinition& step, WorkflowContext& context) {
|
||||
|
||||
float mouseRelX = 0.0f, mouseRelY = 0.0f;
|
||||
bool keyEscapePressed = false;
|
||||
bool keyEnterPressed = false;
|
||||
bool keyUpPressed = false;
|
||||
bool keyDownPressed = false;
|
||||
bool mouseLeftPressed = false;
|
||||
|
||||
SDL_Event event;
|
||||
while (SDL_PollEvent(&event)) {
|
||||
@@ -28,9 +33,20 @@ void WorkflowInputPollStep::Execute(
|
||||
break;
|
||||
case SDL_EVENT_KEY_DOWN:
|
||||
if (event.key.key == SDLK_ESCAPE) {
|
||||
keyEscapePressed = true;
|
||||
} else if (event.key.key == SDLK_RETURN) {
|
||||
keyEnterPressed = true;
|
||||
} else if (event.key.key == SDLK_UP) {
|
||||
keyUpPressed = true;
|
||||
} else if (event.key.key == SDLK_DOWN) {
|
||||
keyDownPressed = true;
|
||||
} else if (event.key.key == SDLK_Q) {
|
||||
context.Set<bool>("game_running", false);
|
||||
}
|
||||
break;
|
||||
case SDL_EVENT_MOUSE_BUTTON_DOWN:
|
||||
if (event.button.button == SDL_BUTTON_LEFT) mouseLeftPressed = true;
|
||||
break;
|
||||
case SDL_EVENT_MOUSE_MOTION:
|
||||
mouseRelX += event.motion.xrel;
|
||||
mouseRelY += event.motion.yrel;
|
||||
@@ -41,6 +57,11 @@ void WorkflowInputPollStep::Execute(
|
||||
// Store accumulated mouse motion for this frame
|
||||
context.Set<float>("input_mouse_rel_x", mouseRelX);
|
||||
context.Set<float>("input_mouse_rel_y", mouseRelY);
|
||||
context.Set<bool>("input_key_escape_pressed", keyEscapePressed);
|
||||
context.Set<bool>("input_key_enter_pressed", keyEnterPressed);
|
||||
context.Set<bool>("input_key_up_pressed", keyUpPressed);
|
||||
context.Set<bool>("input_key_down_pressed", keyDownPressed);
|
||||
context.Set<bool>("input_mouse_left_pressed", mouseLeftPressed);
|
||||
|
||||
// Read keyboard state (snapshot, not event-based)
|
||||
const bool* keyState = SDL_GetKeyboardState(nullptr);
|
||||
@@ -52,6 +73,16 @@ void WorkflowInputPollStep::Execute(
|
||||
context.Set<bool>("input_key_space", keyState[SDL_SCANCODE_SPACE]);
|
||||
context.Set<bool>("input_key_shift", keyState[SDL_SCANCODE_LSHIFT]);
|
||||
context.Set<bool>("input_key_ctrl", keyState[SDL_SCANCODE_LCTRL]);
|
||||
context.Set<bool>("input_mouse_left", (SDL_GetMouseState(nullptr, nullptr) & SDL_BUTTON_LMASK) != 0);
|
||||
context.Set<bool>("input_key_1", keyState[SDL_SCANCODE_1]);
|
||||
context.Set<bool>("input_key_2", keyState[SDL_SCANCODE_2]);
|
||||
context.Set<bool>("input_key_3", keyState[SDL_SCANCODE_3]);
|
||||
context.Set<bool>("input_key_4", keyState[SDL_SCANCODE_4]);
|
||||
context.Set<bool>("input_key_5", keyState[SDL_SCANCODE_5]);
|
||||
context.Set<bool>("input_key_6", keyState[SDL_SCANCODE_6]);
|
||||
context.Set<bool>("input_key_7", keyState[SDL_SCANCODE_7]);
|
||||
context.Set<bool>("input_key_8", keyState[SDL_SCANCODE_8]);
|
||||
context.Set<bool>("input_key_9", keyState[SDL_SCANCODE_9]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
@@ -26,6 +26,11 @@ void WorkflowPhysicsFpsMoveStep::Execute(
|
||||
|
||||
auto* body = context.Get<btRigidBody*>("physics_body_" + playerName, nullptr);
|
||||
if (!body) return;
|
||||
if (context.GetBool("q3.menu_open", false)) {
|
||||
btVector3 vel = body->getLinearVelocity();
|
||||
body->setLinearVelocity(btVector3(0, vel.y(), 0));
|
||||
return;
|
||||
}
|
||||
|
||||
// Read parameters from workflow JSON
|
||||
WorkflowStepParameterResolver paramResolver;
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
#include "services/interfaces/workflow/rendering/workflow_bsp_lightmap_atlas_step.hpp"
|
||||
#include "services/interfaces/workflow/rendering/workflow_bsp_parse_spawn_step.hpp"
|
||||
#include "services/interfaces/workflow/rendering/workflow_bsp_entity_update_step.hpp"
|
||||
#include "services/interfaces/workflow/rendering/workflow_bsp_portal_view_step.hpp"
|
||||
#include "services/interfaces/workflow/rendering/workflow_bsp_build_geometry_step.hpp"
|
||||
#include "services/interfaces/workflow/rendering/workflow_bsp_extract_textures_step.hpp"
|
||||
#include "services/interfaces/workflow/rendering/workflow_bsp_upload_geometry_step.hpp"
|
||||
@@ -112,6 +113,11 @@
|
||||
// Camera (service-dependent)
|
||||
#include "services/interfaces/workflow/workflow_generic_steps/workflow_camera_build_view_state_step.hpp"
|
||||
|
||||
// Quake 3
|
||||
#include "services/interfaces/workflow/quake3/workflow_q3_menu_update_step.hpp"
|
||||
#include "services/interfaces/workflow/quake3/workflow_q3_weapon_update_step.hpp"
|
||||
#include "services/interfaces/workflow/quake3/workflow_q3_overlay_draw_step.hpp"
|
||||
|
||||
// Audio (service-dependent, registered with nullptr)
|
||||
#include "services/interfaces/workflow/workflow_generic_steps/workflow_audio_pause_step.hpp"
|
||||
#include "services/interfaces/workflow/workflow_generic_steps/workflow_audio_play_step.hpp"
|
||||
@@ -297,6 +303,7 @@ void WorkflowRegistrar::RegisterSteps(std::shared_ptr<IWorkflowStepRegistry> reg
|
||||
registry->RegisterStep(std::make_shared<WorkflowBspLightmapAtlasStep>(logger_));
|
||||
registry->RegisterStep(std::make_shared<WorkflowBspParseSpawnStep>(logger_));
|
||||
registry->RegisterStep(std::make_shared<WorkflowBspEntityUpdateStep>(logger_));
|
||||
registry->RegisterStep(std::make_shared<WorkflowBspPortalViewStep>(logger_));
|
||||
registry->RegisterStep(std::make_shared<WorkflowBspBuildGeometryStep>(logger_));
|
||||
registry->RegisterStep(std::make_shared<WorkflowBspExtractTexturesStep>(logger_));
|
||||
registry->RegisterStep(std::make_shared<WorkflowBspUploadGeometryStep>(logger_));
|
||||
@@ -314,6 +321,9 @@ void WorkflowRegistrar::RegisterSteps(std::shared_ptr<IWorkflowStepRegistry> reg
|
||||
registry->RegisterStep(std::make_shared<WorkflowPostfxSsaoStep>(logger_));
|
||||
registry->RegisterStep(std::make_shared<WorkflowPostfxBloomExtractStep>(logger_));
|
||||
registry->RegisterStep(std::make_shared<WorkflowPostfxBloomBlurStep>(logger_));
|
||||
registry->RegisterStep(std::make_shared<WorkflowQ3MenuUpdateStep>(logger_));
|
||||
registry->RegisterStep(std::make_shared<WorkflowQ3WeaponUpdateStep>(logger_));
|
||||
registry->RegisterStep(std::make_shared<WorkflowQ3OverlayDrawStep>(logger_));
|
||||
count += 18;
|
||||
|
||||
// ── Texture ───────────────────────────────────────────────
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "services/interfaces/i_workflow_step.hpp"
|
||||
#include "services/interfaces/i_logger.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace sdl3cpp::services::impl {
|
||||
|
||||
class WorkflowQ3MenuUpdateStep final : public IWorkflowStep {
|
||||
public:
|
||||
explicit WorkflowQ3MenuUpdateStep(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
|
||||
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include "services/interfaces/i_workflow_step.hpp"
|
||||
#include "services/interfaces/i_logger.hpp"
|
||||
#include "services/interfaces/workflow_context.hpp"
|
||||
|
||||
#include <SDL3/SDL_gpu.h>
|
||||
#include <SDL3/SDL_render.h>
|
||||
#include <SDL3/SDL_surface.h>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
namespace sdl3cpp::services::impl {
|
||||
|
||||
class WorkflowQ3OverlayDrawStep final : public IWorkflowStep {
|
||||
public:
|
||||
explicit WorkflowQ3OverlayDrawStep(std::shared_ptr<ILogger> logger);
|
||||
~WorkflowQ3OverlayDrawStep();
|
||||
|
||||
std::string GetPluginId() const override;
|
||||
void Execute(const WorkflowStepDefinition& step, WorkflowContext& context) override;
|
||||
|
||||
private:
|
||||
void TryInit(SDL_GPUDevice* device, SDL_Window* window);
|
||||
void DrawSurface(WorkflowContext& context, uint32_t frameW, uint32_t frameH);
|
||||
void Render(SDL_GPUCommandBuffer* cmd, SDL_GPUTexture* swapchainTex,
|
||||
SDL_GPUDevice* device, uint32_t frameW, uint32_t frameH);
|
||||
|
||||
std::shared_ptr<ILogger> logger_;
|
||||
bool ready_ = false;
|
||||
bool disabled_ = false;
|
||||
bool vbuf_uploaded_ = false;
|
||||
SDL_GPUDevice* device_ = nullptr;
|
||||
SDL_GPUGraphicsPipeline* pipeline_ = nullptr;
|
||||
SDL_GPUTexture* tex_ = nullptr;
|
||||
SDL_GPUTransferBuffer* transfer_ = nullptr;
|
||||
SDL_GPUBuffer* vtx_buf_ = nullptr;
|
||||
SDL_GPUSampler* sampler_ = nullptr;
|
||||
SDL_Surface* surface_ = nullptr;
|
||||
SDL_Renderer* renderer_ = nullptr;
|
||||
|
||||
static constexpr int kW = 640;
|
||||
static constexpr int kH = 360;
|
||||
};
|
||||
|
||||
} // namespace sdl3cpp::services::impl
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "services/interfaces/i_workflow_step.hpp"
|
||||
#include "services/interfaces/i_logger.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace sdl3cpp::services::impl {
|
||||
|
||||
class WorkflowQ3WeaponUpdateStep final : public IWorkflowStep {
|
||||
public:
|
||||
explicit WorkflowQ3WeaponUpdateStep(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
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "services/interfaces/i_workflow_step.hpp"
|
||||
#include "services/interfaces/i_logger.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace sdl3cpp::services::impl {
|
||||
|
||||
class WorkflowBspPortalViewStep final : public IWorkflowStep {
|
||||
public:
|
||||
explicit WorkflowBspPortalViewStep(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