Monopoly — Portage moderne 0.1.0
Portage moderne C/C++23 du Monopoly de 1999
Loading...
Searching...
No Matches
World2DRenderer.cpp
Go to the documentation of this file.
1#include "World2DRenderer.hpp"
3#include "LogicalViewport.hpp"
7#include <SDL3/SDL.h>
8#include <array>
9#include <cstring>
10#include <limits>
11#include <set>
12#include <cmath>
13
14namespace monopoly::engine
15{
17 {
18 if (!device_) return;
19 for (auto& [key, value] : textures_)
20 { (void)key; SDL_ReleaseGPUTexture(device_, value.gpu); }
21 if (quad_) SDL_ReleaseGPUBuffer(device_, quad_);
22 if (sampler_) SDL_ReleaseGPUSampler(device_, sampler_);
23 if (linearSampler_) SDL_ReleaseGPUSampler(device_, linearSampler_);
24 if (pipeline_) SDL_ReleaseGPUGraphicsPipeline(device_, pipeline_);
25 }
26
27 std::expected<std::unique_ptr<World2DRenderer>, std::string> World2DRenderer::load(
28 SDL_GPUDevice* device, const std::filesystem::path& directory,
30 {
32 return std::unexpected("invalid 2D GPU device or target format");
33 auto shaders = World3DShaderSet::load(device, directory);
34 if (!shaders) return std::unexpected(shaders.error().detail);
35 auto result = std::unique_ptr<World2DRenderer>(new World2DRenderer);
36 result->device_ = device;
37 result->shaders_ = std::move(*shaders);
38 // Reuse the existing texture shader with white material/ambient.
39 // 2D owns a separate pipeline: no depth and straight-alpha blending.
41 const std::array<SDL_GPUVertexAttribute,3> attributes{{
46 color.format = colorFormat;
47 color.blend_state.enable_blend = true;
48 color.blend_state.src_color_blendfactor = SDL_GPU_BLENDFACTOR_SRC_ALPHA;
49 color.blend_state.dst_color_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
50 color.blend_state.color_blend_op = SDL_GPU_BLENDOP_ADD;
51 color.blend_state.src_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE;
52 color.blend_state.dst_alpha_blendfactor = SDL_GPU_BLENDFACTOR_ONE_MINUS_SRC_ALPHA;
53 color.blend_state.alpha_blend_op = SDL_GPU_BLENDOP_ADD;
55 info.vertex_shader = result->shaders_.vertex();
56 info.fragment_shader = result->shaders_.fragment();
57 info.vertex_input_state = {&buffer,1,attributes.data(),3};
58 info.primitive_type = SDL_GPU_PRIMITIVETYPE_TRIANGLELIST;
59 info.rasterizer_state.fill_mode = SDL_GPU_FILLMODE_FILL;
60 info.rasterizer_state.cull_mode = SDL_GPU_CULLMODE_NONE;
61 info.multisample_state.sample_count = SDL_GPU_SAMPLECOUNT_1;
62 info.target_info.color_target_descriptions = &color;
63 info.target_info.num_color_targets = 1;
64 result->pipeline_ = SDL_CreateGPUGraphicsPipeline(device, &info);
65 if (!result->pipeline_) return std::unexpected(std::string(SDL_GetError()));
67 sampler.min_filter = sampler.mag_filter = SDL_GPU_FILTER_NEAREST;
68 sampler.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST;
69 sampler.address_mode_u = sampler.address_mode_v = sampler.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
70 result->sampler_ = SDL_CreateGPUSampler(device, &sampler);
71 if (!result->sampler_) return std::unexpected(std::string(SDL_GetError()));
72 sampler.min_filter = sampler.mag_filter = SDL_GPU_FILTER_LINEAR;
73 result->linearSampler_ = SDL_CreateGPUSampler(device, &sampler);
74 if (!result->linearSampler_) return std::unexpected(std::string(SDL_GetError()));
75 const std::array<MeshGPUVertex,6> vertices{{
76 {{0,0,0},{0,0,1},{0,0}}, {{1,0,0},{0,0,1},{1,0}}, {{0,1,0},{0,0,1},{0,1}},
77 {{0,1,0},{0,0,1},{0,1}}, {{1,0,0},{0,0,1},{1,0}}, {{1,1,0},{0,0,1},{1,1}}}};
78 SDL_GPUBufferCreateInfo bi{}; bi.usage=SDL_GPU_BUFFERUSAGE_VERTEX; bi.size=sizeof(vertices);
79 result->quad_=SDL_CreateGPUBuffer(device,&bi);
80 if (!result->quad_) return std::unexpected(std::string(SDL_GetError()));
83 if (!transfer) return std::unexpected(std::string(SDL_GetError()));
84 auto* mapped=SDL_MapGPUTransferBuffer(device,transfer,false);
85 if (!mapped) { SDL_ReleaseGPUTransferBuffer(device,transfer); return std::unexpected(std::string(SDL_GetError())); }
86 std::memcpy(mapped,vertices.data(),sizeof(vertices));
88 auto* command=SDL_AcquireGPUCommandBuffer(device);
89 auto* copy=command ? SDL_BeginGPUCopyPass(command) : nullptr;
90 if (!copy)
91 {
92 if (command) SDL_CancelGPUCommandBuffer(command);
94 return std::unexpected(std::string(SDL_GetError()));
95 }
97 SDL_GPUBufferRegion region{result->quad_,0,sizeof(vertices)};
98 SDL_UploadToGPUBuffer(copy,&location,&region,false);
100 const bool submitted=SDL_SubmitGPUCommandBuffer(command);
102 if (!submitted) return std::unexpected(std::string(SDL_GetError()));
103 return result;
104 }
105
106 std::expected<SDL_GPUTexture*, std::string> World2DRenderer::resolveTexture(
107 const std::shared_ptr<const data::BitmapRuntimeAsset>& asset)
108 {
109 if (!asset) return std::unexpected("missing bitmap asset");
110 if (auto found=textures_.find(asset.get()); found!=textures_.end()) return found->second.gpu;
111 const auto& image=asset->image;
112 const auto size=static_cast<std::uint64_t>(image.width)*image.height*4U;
113 if (!image.width || !image.height || size!=image.pixels.size() || size>std::numeric_limits<Uint32>::max())
114 return std::unexpected("invalid RGBA8 texture extent or payload");
118 ti.width=image.width; ti.height=image.height; ti.layer_count_or_depth=1; ti.num_levels=1;
119 auto* texture=SDL_CreateGPUTexture(device_,&ti);
120 if (!texture) return std::unexpected(std::string(SDL_GetError()));
122 bi.usage=SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD; bi.size=static_cast<Uint32>(size);
124 if (!transfer) { SDL_ReleaseGPUTexture(device_,texture); return std::unexpected(std::string(SDL_GetError())); }
125 auto* mapped=SDL_MapGPUTransferBuffer(device_,transfer,false);
126 if (!mapped)
127 {
129 return std::unexpected(std::string(SDL_GetError()));
130 }
131 std::memcpy(mapped,image.pixels.data(),image.pixels.size());
133 auto* command=SDL_AcquireGPUCommandBuffer(device_);
134 auto* copy=command ? SDL_BeginGPUCopyPass(command) : nullptr;
135 if (!copy)
136 {
137 if (command) SDL_CancelGPUCommandBuffer(command);
139 return std::unexpected(std::string(SDL_GetError()));
140 }
143 destination.texture=texture; destination.w=image.width; destination.h=image.height; destination.d=1;
146 const bool submitted=SDL_SubmitGPUCommandBuffer(command);
148 if (!submitted) { SDL_ReleaseGPUTexture(device_,texture); return std::unexpected(std::string(SDL_GetError())); }
149 textures_.emplace(asset.get(),Texture{texture,asset});
150 return texture;
151 }
152
153 std::expected<std::size_t, std::string> World2DRenderer::render(
155 std::uint32_t width, std::uint32_t height, const SequenceWorld2DSlot& slot)
156 {
157 if (!command || !target || !width || !height ||
158 width>std::numeric_limits<int>::max() || height>std::numeric_limits<int>::max())
159 return std::unexpected("invalid 2D command, target or dimensions");
160 const auto matrixFor = [&slot](const SequenceWorld2DObject& object) {
161 const auto projected = sequence::multiply(object.worldTransform, slot.worldToScreen());
162 const auto& m=projected.values;
163 const auto w=static_cast<float>(object.asset->image.width);
164 const auto h=static_cast<float>(object.asset->image.height);
165 return std::array<float,16>{
166 w*m[0]/400.0F,-w*m[1]/300.0F,0,0,
167 h*m[3]/400.0F,-h*m[4]/300.0F,0,0,
168 0,0,1,0,
169 m[6]/400.0F-1.0F,1.0F-m[7]/300.0F,0,1};
170 };
171 std::set<const data::BitmapRuntimeAsset*> used;
172 const auto suppressed = [this](const SequenceWorld2DObject& object) {
173 // TAB_pointer is a raw bitmap root (root equals contents), not a
174 // shared hover-feedback leaf. Both identity and priority must match.
175 return standardPointerPresentation_ &&
176 object.contentsDataId == mouse::PointerDataId &&
177 object.priority == mouse::PointerPriority && object.asset &&
178 object.asset->dataId == mouse::PointerDataId;
179 };
180 std::size_t drawn{};
181 for (const auto node:slot.order())
182 {
183 const auto* object=slot.find(node);
184 if (!object) return std::unexpected("missing 2D slot node");
185 if (suppressed(*object)) continue;
186 auto texture=resolveTexture(object->asset);
187 if (!texture) return std::unexpected(texture.error());
188 for (const auto value : matrixFor(*object))
189 if (!std::isfinite(value)) return std::unexpected("2D projected matrix exceeds finite GPU range");
190 used.insert(object->asset.get());
191 ++drawn;
192 }
193 for (auto it=textures_.begin();it!=textures_.end();)
194 {
195 if (!used.contains(it->first))
196 { SDL_ReleaseGPUTexture(device_,it->second.gpu); it=textures_.erase(it); }
197 else ++it;
198 }
199 if (drawn==0) return 0;
200 const auto fit=logicalviewport::makeTransform(static_cast<int>(width),static_cast<int>(height));
201 const SDL_GPUViewport viewport{static_cast<float>(fit.offsetX),static_cast<float>(fit.offsetY),
202 static_cast<float>(800.0*fit.scale),static_cast<float>(600.0*fit.scale),0,1};
203 const SDL_Rect scissor{static_cast<int>(std::ceil(fit.offsetX)),static_cast<int>(std::ceil(fit.offsetY)),
204 static_cast<int>(std::floor(fit.offsetX+800.0*fit.scale)-std::ceil(fit.offsetX)),
205 static_cast<int>(std::floor(fit.offsetY+600.0*fit.scale)-std::ceil(fit.offsetY))};
206 SDL_GPUColorTargetInfo color{}; color.texture=target;
207 color.load_op=SDL_GPU_LOADOP_LOAD; color.store_op=SDL_GPU_STOREOP_STORE;
208 auto* pass=SDL_BeginGPURenderPass(command,&color,1,nullptr);
209 if (!pass) return std::unexpected(std::string(SDL_GetError()));
214 fragmentUniforms.materialDiffuse = {1, 1, 1, 1};
218 vertexUniforms.materialDiffuse = {1, 1, 1, 1};
219 vertexUniforms.sceneAmbient = {1, 1, 1, 1};
220 // Remaining zero-initialized fields disable all 3D lights for 2D.
221 for (const auto node:slot.order())
222 {
223 const auto& object=*slot.find(node);
224 if (suppressed(object)) continue;
225 // Row-vector Matrix2D into the original 800x600 logical canvas.
226 // DataBMP origin is (0,0); no dice-specific anchor is inserted.
227 vertexUniforms.worldViewProjection = matrixFor(object);
229 SDL_GPUTextureSamplerBinding sample{textures_.at(object.asset.get()).gpu,
230 object.asset->preferLinearFiltering ? linearSampler_ : sampler_};
233 }
235 return drawn;
236 }
237}
LegacyBitmapRGBA8 image
PlayerNumber slot
std::shared_ptr< const data::HmdTextureImage > source
SDL_GPUTexture * texture
std::uint64_t key
std::uint32_t height
std::uint32_t width
unsigned index
std::filesystem::path directory
std::uint16_t value
Definition Timers.cpp:16
static std::expected< std::unique_ptr< World2DRenderer >, std::string > load(SDL_GPUDevice *device, const std::filesystem::path &shaders, SDL_GPUTextureFormat colorFormat)
std::expected< std::size_t, std::string > render(SDL_GPUCommandBuffer *command, SDL_GPUTexture *target, std::uint32_t width, std::uint32_t height, const SequenceWorld2DSlot &slot)
static std::expected< World3DShaderSet, World3DShaderError > load(SDL_GPUDevice *device, const std::filesystem::path &shaderDirectory, bool modernPBR=false)
Transform makeTransform(int targetWidth, int targetHeight) noexcept
constexpr data::DataId PointerDataId
constexpr std::uint16_t PointerPriority
Matrix3D identity3D() noexcept
Matrix2D multiply(const Matrix2D &left, const Matrix2D &right) noexcept
std::array< float, 16 > values