Monopoly — Portage moderne 0.1.0
Portage moderne C/C++23 du Monopoly de 1999
Loading...
Searching...
No Matches
World3DRenderer.cpp
Go to the documentation of this file.
1#include "World3DRenderer.hpp"
2
6
7#include <SDL3/SDL.h>
8
9#include <algorithm>
10#include <array>
11#include <cmath>
12#include <cstring>
13#include <limits>
14#include <utility>
15
16namespace monopoly::engine
17{
18 namespace
19 {
20 constexpr std::uint32_t ShadowSize = 2048U;
21 std::optional<sequence::Matrix3D> shadowProjection(const SequenceWorld3DSlot& slot,
22 const std::array<float, 3>& direction)
23 {
24 using Vector = std::array<float, 3>;
25 const auto dot = [](const Vector& a, const Vector& b)
26 { return a[0]*b[0] + a[1]*b[1] + a[2]*b[2]; };
27 const auto cross = [](const Vector& a, const Vector& b) -> Vector
28 { return {a[1]*b[2]-a[2]*b[1], a[2]*b[0]-a[0]*b[2], a[0]*b[1]-a[1]*b[0]}; };
29 const auto normalized = [&](Vector a) -> Vector
30 { const float length = std::sqrt(dot(a,a)); for (auto& v : a) v /= length; return a; };
31 if (!std::isfinite(dot(direction,direction)) || dot(direction,direction) < 0.000001F)
32 return std::nullopt;
33 const auto forward = normalized(direction);
34 const auto right = normalized(cross(std::abs(forward[1]) > 0.95F
35 ? Vector{0,0,1} : Vector{0,1,0}, forward));
36 const auto up = cross(forward,right);
38 bool found = false;
39 // Fit the actual evaluated asset bounds after the driven world
40 // matrices, never a presentation-specific game-coordinate guess.
41 for (const auto id : slot.visibleOrder())
42 {
43 const auto* object = slot.find(id);
44 if (!object || !object->asset || object->asset->origin != data::MeshAssetOrigin::ModernGltf)
45 continue;
46 const auto mesh = object->renderData ? object->renderData : object->asset->renderData;
47 if (!mesh) continue;
48 const auto& m = object->worldTransform.values;
49 for (int corner = 0; corner < 8; ++corner)
50 {
51 Vector local{};
52 for (int axis=0; axis<3; ++axis)
53 local[axis] = (corner & (1 << axis)) ? mesh->bounds.maximum[axis] : mesh->bounds.minimum[axis];
54 const Vector world{local[0]*m[0]+local[1]*m[4]+local[2]*m[8]+m[12],
55 local[0]*m[1]+local[1]*m[5]+local[2]*m[9]+m[13],
56 local[0]*m[2]+local[1]*m[6]+local[2]*m[10]+m[14]};
57 const Vector light{dot(world,right),dot(world,up),dot(world,forward)};
58 for (int axis=0; axis<3; ++axis)
59 { low[axis]=std::min(low[axis],light[axis]); high[axis]=std::max(high[axis],light[axis]); }
60 found = true;
61 }
62 }
63 if (!found) return std::nullopt;
64 Vector span{};
65 for (int axis=0; axis<3; ++axis)
66 {
67 if (!std::isfinite(low[axis]) || !std::isfinite(high[axis])) return std::nullopt;
68 const float margin = std::max((high[axis]-low[axis])*0.04F, 1.0F);
70 }
71 auto result = sequence::identity3D();
72 for (int axis=0; axis<3; ++axis)
73 {
74 result.values[axis*4] = right[axis]*2/span[0];
75 result.values[axis*4+1] = up[axis]*2/span[1];
76 result.values[axis*4+2] = forward[axis]/span[2];
77 }
78 result.values[12]=-(high[0]+low[0])/span[0];
79 result.values[13]=-(high[1]+low[1])/span[1];
80 result.values[14]=-low[2]/span[2];
81 return result;
82 }
83 [[nodiscard]] std::array<float, 4> vector4(
84 const std::array<float, 3>& value, float w = 0.0F) noexcept
85 {
86 return {value[0], value[1], value[2], w};
87 }
88
89 bool validViewport(const SDL_GPUViewport& viewport,
90 std::uint32_t width, std::uint32_t height) noexcept
91 {
92 return std::isfinite(viewport.x) && std::isfinite(viewport.y) &&
93 std::isfinite(viewport.w) && std::isfinite(viewport.h) &&
94 viewport.x >= 0.0F && viewport.y >= 0.0F &&
95 viewport.w > 0.0F && viewport.h > 0.0F &&
96 viewport.x + viewport.w <= static_cast<float>(width) &&
97 viewport.y + viewport.h <= static_cast<float>(height) &&
98 viewport.min_depth >= 0.0F && viewport.max_depth <= 1.0F &&
99 viewport.min_depth <= viewport.max_depth;
100 }
101
102 World3DRendererError rendererError(
103 World3DRendererErrorCode code, const char* fallback)
104 {
105 World3DRendererError result;
106 result.code = code;
107 const char* sdl = SDL_GetError();
108 result.detail = sdl != nullptr && *sdl != '\0' ? sdl : fallback;
109 return result;
110 }
111
112 std::expected<SDL_GPUTexture*, World3DRendererError>
114 {
119 textureInfo.width = 1U;
120 textureInfo.height = 1U;
121 textureInfo.layer_count_or_depth = 1U;
122 textureInfo.num_levels = 1U;
123 textureInfo.sample_count = SDL_GPU_SAMPLECOUNT_1;
125 if (texture == nullptr)
126 return std::unexpected(rendererError(
128 "could not create 1x1 white World3D fallback texture"));
129
130 // 256-byte row pitch keeps the upload on SDL's direct D3D12 path.
133 transferInfo.size = 256U;
136 if (transfer == nullptr)
137 {
139 return std::unexpected(rendererError(
141 "could not create fallback texture upload buffer"));
142 }
143
144 void* mapped = SDL_MapGPUTransferBuffer(device, transfer, false);
145 if (mapped == nullptr)
146 {
149 return std::unexpected(rendererError(
151 "could not map fallback texture upload buffer"));
152 }
153 std::memset(mapped, 0, 256U);
154 auto* pixel = static_cast<std::uint8_t*>(mapped);
155 pixel[0] = pixel[1] = pixel[2] = pixel[3] = 255U;
157
159 if (command == nullptr)
160 {
163 return std::unexpected(rendererError(
165 "could not acquire fallback texture upload command buffer"));
166 }
168 if (copy == nullptr)
169 {
173 return std::unexpected(rendererError(
175 "could not begin fallback texture upload copy pass"));
176 }
177
179 source.transfer_buffer = transfer;
180 source.pixels_per_row = 64U;
181 source.rows_per_layer = 1U;
183 destination.texture = texture;
184 destination.w = 1U;
185 destination.h = 1U;
186 destination.d = 1U;
189
190 if (!SDL_SubmitGPUCommandBuffer(command))
191 {
194 return std::unexpected(rendererError(
196 "could not submit fallback texture upload"));
197 }
199 return texture;
200 }
201 }
202
207
209 {
210 *this = std::move(other);
211 }
212
214 {
215 if (this == &other) return *this;
216 reset();
217 device_ = std::exchange(other.device_, nullptr);
218 pipeline_ = std::move(other.pipeline_);
219 modernPipeline_ = std::move(other.modernPipeline_);
220 modernPipelineAttempted_ = std::exchange(other.modernPipelineAttempted_, false);
221 shaderDirectory_ = std::move(other.shaderDirectory_);
222 colorFormat_ = std::exchange(other.colorFormat_, SDL_GPU_TEXTUREFORMAT_INVALID);
223 meshCache_ = std::move(other.meshCache_);
224 studioEnvironment_ = std::move(other.studioEnvironment_);
225 studioEnvironmentEnabled_ = std::exchange(other.studioEnvironmentEnabled_, false);
226 textureSampler_ = std::exchange(other.textureSampler_, nullptr);
227 linearSampler_ = std::exchange(other.linearSampler_, nullptr);
228 bilinearFiltering_ = std::exchange(other.bilinearFiltering_, false);
229 whiteTexture_ = std::exchange(other.whiteTexture_, nullptr);
230 depthTarget_ = std::exchange(other.depthTarget_, nullptr);
231 depthWidth_ = std::exchange(other.depthWidth_, 0U);
232 depthHeight_ = std::exchange(other.depthHeight_, 0U);
233 lighting_ = other.lighting_;
234 other.lighting_ = {};
235 modernPresentation_ = std::exchange(other.modernPresentation_, false);
236 presentationShadows_ = std::exchange(other.presentationShadows_, false);
237 presentationAntialiasing_ = std::exchange(other.presentationAntialiasing_, false);
238 shadowsActive_ = std::exchange(other.shadowsActive_, false);
239 antialiasingActive_ = std::exchange(other.antialiasingActive_, false);
240 depthSamples_ = std::exchange(other.depthSamples_, SDL_GPU_SAMPLECOUNT_1);
241 multisampleColor_ = std::exchange(other.multisampleColor_, nullptr);
242 multisampleWidth_ = std::exchange(other.multisampleWidth_, 0U);
243 multisampleHeight_ = std::exchange(other.multisampleHeight_, 0U);
244 multisampleLegacyPipeline_ = std::move(other.multisampleLegacyPipeline_);
245 multisampleModernPipeline_ = std::move(other.multisampleModernPipeline_);
246 shadowMap_ = std::exchange(other.shadowMap_, nullptr);
247 shadowDepth_ = std::exchange(other.shadowDepth_, nullptr);
248 shadowSampler_ = std::exchange(other.shadowSampler_, nullptr);
249 shadowMapPipeline_ = std::move(other.shadowMapPipeline_);
250 return *this;
251 }
252
253 void World3DRenderer::releaseDepthTarget() noexcept
254 {
255 if (device_ && depthTarget_)
256 SDL_ReleaseGPUTexture(device_, depthTarget_);
257 depthTarget_ = nullptr;
258 depthWidth_ = 0U;
259 depthHeight_ = 0U;
260 depthSamples_ = SDL_GPU_SAMPLECOUNT_1;
261 }
262
263 void World3DRenderer::releasePresentationResources() noexcept
264 {
265 if (device_ && multisampleColor_) SDL_ReleaseGPUTexture(device_, multisampleColor_);
266 if (device_ && shadowMap_) SDL_ReleaseGPUTexture(device_, shadowMap_);
267 if (device_ && shadowDepth_) SDL_ReleaseGPUTexture(device_, shadowDepth_);
268 if (device_ && shadowSampler_) SDL_ReleaseGPUSampler(device_, shadowSampler_);
269 multisampleColor_ = shadowMap_ = shadowDepth_ = nullptr;
270 shadowSampler_ = nullptr;
271 multisampleWidth_ = multisampleHeight_ = 0U;
272 multisampleLegacyPipeline_.reset();
273 multisampleModernPipeline_.reset();
274 shadowMapPipeline_.reset();
275 }
276
277 bool World3DRenderer::ensureShadowResources()
278 {
279 if (shadowMap_ && shadowDepth_ && shadowMapPipeline_ && shadowSampler_) return true;
282 return false;
283 if (!shadowMapPipeline_)
284 {
285 auto pipeline = World3DPipeline::load(device_, shaderDirectory_, SDL_GPU_TEXTUREFORMAT_R32_FLOAT, true);
286 if (!pipeline) return false;
287 shadowMapPipeline_ = std::move(*pipeline);
288 }
290 info.type = SDL_GPU_TEXTURETYPE_2D;
291 info.width = info.height = ShadowSize;
292 info.layer_count_or_depth = info.num_levels = 1U;
293 info.sample_count = SDL_GPU_SAMPLECOUNT_1;
294 if (!shadowMap_)
295 {
298 shadowMap_ = SDL_CreateGPUTexture(device_, &info);
299 }
300 if (!shadowDepth_)
301 {
302 info.format = shadowMapPipeline_->depthFormat();
304 shadowDepth_ = SDL_CreateGPUTexture(device_, &info);
305 }
306 if (!shadowSampler_)
307 {
308 SDL_GPUSamplerCreateInfo sampler{};
309 sampler.min_filter = sampler.mag_filter = SDL_GPU_FILTER_NEAREST;
310 sampler.mipmap_mode = SDL_GPU_SAMPLERMIPMAPMODE_NEAREST;
311 sampler.address_mode_u = sampler.address_mode_v = sampler.address_mode_w = SDL_GPU_SAMPLERADDRESSMODE_CLAMP_TO_EDGE;
312 shadowSampler_ = SDL_CreateGPUSampler(device_, &sampler);
313 }
314 return shadowMap_ && shadowDepth_ && shadowSampler_;
315 }
316
317 bool World3DRenderer::ensurePresentationTargets(std::uint32_t width, std::uint32_t height)
318 {
319 if (!SDL_GPUTextureSupportsSampleCount(device_, colorFormat_, SDL_GPU_SAMPLECOUNT_4) ||
321 return false;
322 if (!multisampleLegacyPipeline_)
323 {
324 auto pipeline = World3DPipeline::load(device_, shaderDirectory_, colorFormat_, false, SDL_GPU_SAMPLECOUNT_4);
325 if (!pipeline) return false;
326 multisampleLegacyPipeline_ = std::move(*pipeline);
327 }
328 if (!multisampleModernPipeline_)
329 {
330 auto pipeline = World3DPipeline::load(device_, shaderDirectory_, colorFormat_, true, SDL_GPU_SAMPLECOUNT_4);
331 if (!pipeline) return false;
332 multisampleModernPipeline_ = std::move(*pipeline);
333 }
334 if (multisampleColor_ && multisampleWidth_ == width && multisampleHeight_ == height) return true;
336 info.type = SDL_GPU_TEXTURETYPE_2D;
337 info.format = colorFormat_;
339 info.width = width; info.height = height;
340 info.layer_count_or_depth = info.num_levels = 1U;
341 info.sample_count = SDL_GPU_SAMPLECOUNT_4;
342 auto* replacement = SDL_CreateGPUTexture(device_, &info);
343 if (!replacement) return false;
344 if (multisampleColor_) SDL_ReleaseGPUTexture(device_, multisampleColor_);
345 multisampleColor_ = replacement;
346 multisampleWidth_ = width; multisampleHeight_ = height;
347 return true;
348 }
349
350 void World3DRenderer::releaseSamplingResources() noexcept
351 {
352 if (device_ && whiteTexture_)
353 SDL_ReleaseGPUTexture(device_, whiteTexture_);
354 if (device_ && textureSampler_)
355 SDL_ReleaseGPUSampler(device_, textureSampler_);
356 if (device_ && linearSampler_)
357 SDL_ReleaseGPUSampler(device_, linearSampler_);
358 whiteTexture_ = nullptr;
359 textureSampler_ = nullptr;
360 linearSampler_ = nullptr;
361 }
362
364 {
365 releaseDepthTarget();
366 releasePresentationResources();
367 if (meshCache_) meshCache_->clear();
368 meshCache_.reset();
369 studioEnvironment_.reset();
370 studioEnvironmentEnabled_ = false;
371 releaseSamplingResources();
372 pipeline_.reset();
373 modernPipeline_.reset();
374 modernPipelineAttempted_ = false;
375 shaderDirectory_.clear();
376 colorFormat_ = SDL_GPU_TEXTUREFORMAT_INVALID;
377 device_ = nullptr;
378 lighting_ = {};
379 bilinearFiltering_ = false;
380 modernPresentation_ = presentationShadows_ = presentationAntialiasing_ = false;
381 shadowsActive_ = antialiasingActive_ = false;
382 }
383
384 bool World3DRenderer::ensureDepthTarget(
385 std::uint32_t width, std::uint32_t height, SDL_GPUSampleCount samples) noexcept
386 {
387 if (depthTarget_ && depthWidth_ == width && depthHeight_ == height && depthSamples_ == samples)
388 return true;
389
391 info.type = SDL_GPU_TEXTURETYPE_2D;
392 info.format = pipeline_.depthFormat();
394 info.width = width;
395 info.height = height;
396 info.layer_count_or_depth = 1U;
397 info.num_levels = 1U;
398 info.sample_count = samples;
399 SDL_GPUTexture* replacement = SDL_CreateGPUTexture(device_, &info);
400 if (!replacement) return false;
401
402 releaseDepthTarget();
403 depthTarget_ = replacement;
404 depthWidth_ = width;
405 depthHeight_ = height;
406 depthSamples_ = samples;
407 return true;
408 }
409
410 std::expected<World3DRenderer, World3DRendererError> World3DRenderer::load(
411 SDL_GPUDevice* device,
412 const std::filesystem::path& shaderDirectory,
414 const std::filesystem::path& studioEnvironmentPath)
415 {
416 if (!device)
417 return std::unexpected(World3DRendererError{
419 "missing SDL GPU device", {}, {}});
420
422 if (!pipeline)
423 return std::unexpected(World3DRendererError{
425 pipeline.error().detail, pipeline.error(), {}});
426
427 auto whiteTexture = createWhiteTexture(device);
428 if (!whiteTexture) return std::unexpected(whiteTexture.error());
429
430 auto environment = StudioEnvironmentGPU::blackFallback(device);
431 if (!environment)
432 {
434 return std::unexpected(World3DRendererError{
436 environment.error().detail, {}, {}});
437 }
438 bool environmentEnabled = false;
439 if (!studioEnvironmentPath.empty())
440 {
443 std::expected<StudioEnvironmentGPU, StudioEnvironmentError>{std::unexpected(data.error())};
444 if (uploaded)
445 {
446 environment = std::move(uploaded);
447 environmentEnabled = true;
448 }
450 "Optional studio environment disabled: %s", uploaded.error().detail.c_str());
451 }
452
454 // Source/PC3D/pc3d.cpp: retail filters are POINT/POINT/NONE.
461 samplerInfo.min_lod = 0.0F;
462 samplerInfo.max_lod = 0.0F;
464 if (sampler == nullptr)
465 {
467 return std::unexpected(rendererError(
469 "could not create retail-compatible World3D point sampler"));
470 }
471
472 // RenderSettings.inl toggles stage-0 MIN/MAG only; preserve wrap and
473 // disabled mipmapping for both samplers. The 2D overlay is unaffected.
474 samplerInfo.min_filter = samplerInfo.mag_filter = SDL_GPU_FILTER_LINEAR;
476 if (!linearSampler)
477 {
478 SDL_ReleaseGPUSampler(device, sampler);
481 "could not create World3D bilinear sampler"));
482 }
483 World3DRenderer result;
484 result.device_ = device;
485 result.whiteTexture_ = *whiteTexture;
486 result.textureSampler_ = sampler;
487 result.linearSampler_ = linearSampler;
488 result.studioEnvironment_ = std::move(*environment);
489 result.studioEnvironmentEnabled_ = environmentEnabled;
490 result.pipeline_ = std::move(*pipeline);
491 result.shaderDirectory_ = shaderDirectory;
492 result.colorFormat_ = colorFormat;
493 result.meshCache_ = std::make_unique<MeshGPUCache>(device);
494 return result;
495 }
496
497 std::expected<World3DRenderStats, World3DRendererError>
501 std::uint32_t targetWidth,
502 std::uint32_t targetHeight,
503 const SDL_GPUViewport& viewport,
505 {
506 if (!commandBuffer)
507 return std::unexpected(World3DRendererError{
509 "missing SDL GPU command buffer", {}, {}});
510 if (!colorTarget)
511 return std::unexpected(World3DRendererError{
513 "missing World3D color target", {}, {}});
514 if (targetWidth == 0U || targetHeight == 0U ||
516 return std::unexpected(World3DRendererError{
518 "invalid World3D target or viewport", {}, {}});
519 if (!slot.view())
520 return std::unexpected(World3DRendererError{
522 "World3D slot has no configured camera/view", {}, {}});
523
524 auto batches = buildWorld3DGPUScene(slot, *meshCache_);
525 if (!batches)
526 return std::unexpected(World3DRendererError{
528 batches.error().detail, {}, batches.error()});
529
530 const bool needsModernPipeline = std::any_of(batches->begin(), batches->end(),
532 {
533 return !batch.legacyShadow &&
534 batch.material.model == data::MeshMaterialModel::MetallicRoughness;
535 });
536 if (needsModernPipeline && !modernPipelineAttempted_)
537 {
538 modernPipelineAttempted_ = true;
539 auto modern = World3DPipeline::load(device_, shaderDirectory_, colorFormat_, true);
540 if (modern)
541 modernPipeline_ = std::move(*modern);
542 else
544 "Modern PBR pipeline unavailable; drawing modern meshes with legacy diffuse: %s",
545 modern.error().detail.c_str());
546 }
547
548 shadowsActive_ = antialiasingActive_ = false;
549 World3DRenderStats stats;
550 const auto lightProjection = presentationShadows_ && lighting_.sun.enabled && modernPipeline_
551 ? shadowProjection(slot, lighting_.sun.direction) : std::nullopt;
552 if (lightProjection && ensureShadowResources())
553 {
555 shadowColor.texture = shadowMap_;
556 shadowColor.clear_color = {1,0,0,1};
559 shadowColor.cycle = true;
561 shadowDepth.texture = shadowDepth_;
562 shadowDepth.clear_depth = 1;
565 shadowDepth.stencil_load_op = SDL_GPU_LOADOP_DONT_CARE;
566 shadowDepth.stencil_store_op = SDL_GPU_STOREOP_DONT_CARE;
567 shadowDepth.cycle = true;
569 if (!shadowPass)
571 "could not begin presentation shadow pass"));
572 const SDL_GPUViewport shadowViewport{0,0,static_cast<float>(ShadowSize),static_cast<float>(ShadowSize),0,1};
574 SDL_BindGPUGraphicsPipeline(shadowPass, shadowMapPipeline_->doubleSidedHandle());
575 for (const auto& batch : *batches)
576 {
577 if (batch.legacyShadow || batch.material.model != data::MeshMaterialModel::MetallicRoughness)
578 continue;
580 vertex.world = batch.worldTransform.values;
583 fragment.baseColor = batch.material.diffuse;
584 fragment.metallicRoughness[3] = batch.material.alphaMode == data::ModernAlphaMode::Mask ? 1.0F : 0.0F;
585 fragment.mapFlags[0] = batch.material.baseColorTexture ? 1.0F : 0.0F;
586 fragment.mapParameters[3] = batch.material.alphaCutoff;
587 fragment.shadowParameters[0] = -1.0F;
588 SDL_PushGPUVertexUniformData(commandBuffer,0U,&vertex,static_cast<Uint32>(sizeof(vertex)));
589 SDL_PushGPUFragmentUniformData(commandBuffer,0U,&fragment,static_cast<Uint32>(sizeof(fragment)));
590 const SDL_GPUBufferBinding vertices{batch.vertexBuffer,0U}, indices{batch.indexBuffer,0U};
593 std::array<SDL_GPUTextureSamplerBinding,7> textures{};
594 for (auto& texture : textures) texture = {whiteTexture_,linearSampler_};
595 if (batch.modernTextures[0].texture) textures[0]=batch.modernTextures[0];
596 textures[5]={studioEnvironment_.texture(),studioEnvironment_.sampler()};
597 SDL_BindGPUFragmentSamplers(shadowPass,0U,textures.data(),static_cast<Uint32>(textures.size()));
598 SDL_DrawGPUIndexedPrimitives(shadowPass,batch.indexCount,1U,batch.firstIndex,0,0U);
599 ++stats.shadowBatches;
600 stats.shadowTriangles += batch.indexCount/3U;
601 }
603 shadowsActive_ = stats.shadowBatches > 0;
604 }
605 const bool fullTarget = viewport.x == 0 && viewport.y == 0 &&
606 viewport.w == static_cast<float>(targetWidth) && viewport.h == static_cast<float>(targetHeight);
607 antialiasingActive_ = presentationAntialiasing_ && needsModernPipeline && modernPipeline_ && fullTarget &&
608 ensurePresentationTargets(targetWidth,targetHeight);
610 if (!ensureDepthTarget(targetWidth, targetHeight, antialiasingActive_ ? SDL_GPU_SAMPLECOUNT_4 : SDL_GPU_SAMPLECOUNT_1))
611 return std::unexpected(World3DRendererError{
613 SDL_GetError(), {}, {}});
614
616 color.texture = colorTarget;
617 color.load_op = SDL_GPU_LOADOP_LOAD;
618 color.store_op = SDL_GPU_STOREOP_STORE;
619 color.cycle = false;
620 if (antialiasingActive_)
621 {
622 color.texture = multisampleColor_;
623 color.clear_color = {0.035F,0.04F,0.05F,1.0F};
624 color.load_op = SDL_GPU_LOADOP_CLEAR;
625 color.store_op = SDL_GPU_STOREOP_RESOLVE;
626 color.resolve_texture = colorTarget;
627 color.cycle = true;
628 }
629
631 depth.texture = depthTarget_;
632 depth.clear_depth = 1.0F;
633 depth.load_op = SDL_GPU_LOADOP_CLEAR;
635 depth.stencil_load_op = SDL_GPU_LOADOP_DONT_CARE;
636 depth.stencil_store_op = SDL_GPU_STOREOP_DONT_CARE;
637 depth.cycle = true;
639 commandBuffer, &color, 1U, &depth);
640 if (!pass)
641 return std::unexpected(World3DRendererError{
643 SDL_GetError(), {}, {}});
644
645 SDL_SetGPUViewport(pass, &viewport);
646
648 stats.objects = slot.visibleOrder().size();
649 stats.batches = batches->size();
650 const auto& projection = *slot.view();
651
652 for (const auto& batch : *batches)
653 {
654 const bool modern = modernPipeline_ && !batch.legacyShadow &&
656 const auto& legacyPipeline = antialiasingActive_ ? *multisampleLegacyPipeline_ : pipeline_;
657 const auto* modernPipeline = antialiasingActive_ ? &*multisampleModernPipeline_ :
658 (modernPipeline_ ? &*modernPipeline_ : nullptr);
660 ? legacyPipeline.shadowHandle()
661 : modern ? (batch.material.doubleSided
663 : legacyPipeline.handle();
665 {
668 }
669
670 const auto worldView = sequence::multiply(
671 batch.worldTransform, projection.view);
672 const auto worldViewProjection = sequence::multiply(
673 worldView, projection.rasterProjection);
674
676 vertexUniforms.worldViewProjection = worldViewProjection.values;
677 vertexUniforms.world = batch.worldTransform.values;
678 vertexUniforms.materialDiffuse = batch.material.diffuse;
679
681 fragmentUniforms.materialDiffuse = batch.material.diffuse;
682 vertexUniforms.sceneAmbient = vector4(lighting_.ambient, 1.0F);
683 vertexUniforms.boardReflectionColorEnabled = vector4(
684 lighting_.boardReflection.color,
685 lighting_.boardReflection.enabled ? 1.0F : 0.0F);
686 vertexUniforms.boardReflectionDirection = vector4(
687 lighting_.boardReflection.direction);
688 vertexUniforms.sunColorEnabled = vector4(
689 lighting_.sun.color, lighting_.sun.enabled ? 1.0F : 0.0F);
690 vertexUniforms.sunDirection = vector4(lighting_.sun.direction);
691 vertexUniforms.spotlightColorEnabled = vector4(
692 lighting_.spotlight.color,
693 lighting_.spotlight.enabled ? 1.0F : 0.0F);
694 vertexUniforms.spotlightPositionRange = vector4(
695 lighting_.spotlight.position, lighting_.spotlight.range);
696 vertexUniforms.spotlightDirectionFalloff = vector4(
697 lighting_.spotlight.direction, lighting_.spotlight.falloff);
698 vertexUniforms.spotlightAttenuationTheta = vector4(
699 lighting_.spotlight.attenuation, lighting_.spotlight.theta);
700 vertexUniforms.spotlightPhi =
701 {lighting_.spotlight.phi, 0.0F, 0.0F, 0.0F};
702
703 if (modern)
704 {
706 modernVertex.worldViewProjection = worldViewProjection.values;
707 modernVertex.world = batch.worldTransform.values;
709 modernFragment.baseColor = batch.material.diffuse;
710 const bool targetSRGB = colorFormat_ == SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM_SRGB ||
712 modernFragment.metallicRoughness = {batch.material.metallic,
713 batch.material.roughness, targetSRGB ? 1.0F : 0.0F,
714 batch.material.alphaMode == data::ModernAlphaMode::Mask ? 1.0F : 0.0F};
715 modernFragment.emissiveStrength = vector4(
716 batch.material.emissive, batch.material.emissiveStrength);
717 modernFragment.cameraPosition = vector4(projection.camera.location, 1.0F);
718 modernFragment.sceneAmbient = vertexUniforms.sceneAmbient;
719 modernFragment.sceneAmbient[3] = studioEnvironmentEnabled_ ? 1.0F : 0.0F;
720 modernFragment.boardReflectionColorEnabled = vertexUniforms.boardReflectionColorEnabled;
721 modernFragment.boardReflectionDirection = vertexUniforms.boardReflectionDirection;
722 modernFragment.sunColorEnabled = vertexUniforms.sunColorEnabled;
723 modernFragment.sunDirection = vertexUniforms.sunDirection;
724 modernFragment.spotlightColorEnabled = vertexUniforms.spotlightColorEnabled;
725 modernFragment.spotlightPositionRange = vertexUniforms.spotlightPositionRange;
726 modernFragment.spotlightDirectionFalloff = vertexUniforms.spotlightDirectionFalloff;
727 modernFragment.spotlightAttenuationTheta = vertexUniforms.spotlightAttenuationTheta;
728 modernFragment.spotlightPhi = vertexUniforms.spotlightPhi;
729 modernFragment.mapFlags = {
730 batch.material.baseColorTexture ? 1.0F : 0.0F,
731 batch.material.metallicRoughnessTexture ? 1.0F : 0.0F,
732 batch.material.normalTexture ? 1.0F : 0.0F,
733 batch.material.emissiveTexture ? 1.0F : 0.0F};
734 modernFragment.mapParameters = {
735 batch.material.occlusionTexture ? 1.0F : 0.0F,
736 batch.material.normalTexture ? batch.material.normalTexture->scale : 1.0F,
737 batch.material.occlusionTexture ? batch.material.occlusionTexture->scale : 1.0F,
738 batch.material.alphaCutoff};
739 if (lightProjection) modernFragment.shadowViewProjection = lightProjection->values;
740 modernFragment.shadowParameters = {shadowsActive_ ? 1.0F : 0.0F,
741 1.0F/static_cast<float>(ShadowSize),0.00025F,modernPresentation_ ? 1.0F : 0.0F};
743 &modernVertex, static_cast<Uint32>(sizeof(modernVertex)));
745 &modernFragment, static_cast<Uint32>(sizeof(modernFragment)));
746 }
747 else
748 {
750 &vertexUniforms, static_cast<Uint32>(sizeof(vertexUniforms)));
752 &fragmentUniforms, static_cast<Uint32>(sizeof(fragmentUniforms)));
753 }
754
755 const SDL_GPUBufferBinding vertexBinding{batch.vertexBuffer, 0U};
756 const SDL_GPUBufferBinding indexBinding{batch.indexBuffer, 0U};
760
762 batch.gpuTexture != nullptr ? batch.gpuTexture : whiteTexture_,
763 bilinearFiltering_ ? linearSampler_ : textureSampler_};
764 if (modern)
765 {
766 std::array<SDL_GPUTextureSamplerBinding, 7> maps{};
767 std::copy(batch.modernTextures.begin(), batch.modernTextures.end(), maps.begin());
768 for (std::size_t map = 0; map < 5U; ++map)
769 {
770 auto& binding = maps[map];
771 if (!binding.texture) binding = {whiteTexture_, linearSampler_};
772 }
773 maps[5] = {studioEnvironment_.texture(), studioEnvironment_.sampler()};
774 maps[6] = shadowsActive_ ? SDL_GPUTextureSamplerBinding{shadowMap_,shadowSampler_}
775 : SDL_GPUTextureSamplerBinding{whiteTexture_,linearSampler_};
777 static_cast<Uint32>(maps.size()));
778 }
780
782 batch.indexCount, 1U, batch.firstIndex, 0, 0U);
783 stats.triangles += batch.indexCount / 3U;
784 }
785
787 return stats;
788 }
789}
std::vector< std::uint8_t > data
std::atomic< float > right
bool uploaded
Definition Engine.cpp:170
PlayerNumber slot
std::shared_ptr< const data::HmdTextureImage > source
SDL_GPUTexture * texture
std::uint32_t height
std::uint32_t width
unsigned index
std::uint16_t value
Definition Timers.cpp:16
CardTag other
static std::expected< StudioEnvironmentGPU, StudioEnvironmentError > upload(SDL_GPUDevice *, const StudioEnvironmentData &)
SDL_GPUTexture * texture() const noexcept
static std::expected< StudioEnvironmentGPU, StudioEnvironmentError > blackFallback(SDL_GPUDevice *)
SDL_GPUSampler * sampler() const noexcept
SDL_GPUGraphicsPipeline * handle() const noexcept
SDL_GPUGraphicsPipeline * doubleSidedHandle() const noexcept
SDL_GPUTextureFormat depthFormat() const noexcept
static std::expected< World3DPipeline, World3DPipelineError > load(SDL_GPUDevice *device, const std::filesystem::path &shaderDirectory, SDL_GPUTextureFormat colorFormat, bool modernPBR=false, SDL_GPUSampleCount samples=SDL_GPU_SAMPLECOUNT_1)
const World3DPipeline * modernPipeline() const noexcept
World3DRenderer & operator=(const World3DRenderer &)=delete
std::uint32_t presentationSampleCount() const noexcept
static std::expected< World3DRenderer, World3DRendererError > load(SDL_GPUDevice *device, const std::filesystem::path &shaderDirectory, SDL_GPUTextureFormat colorFormat, const std::filesystem::path &studioEnvironmentPath={})
const World3DPipeline & pipeline() const noexcept
std::expected< World3DRenderStats, World3DRendererError > render(SDL_GPUCommandBuffer *commandBuffer, SDL_GPUTexture *colorTarget, std::uint32_t targetWidth, std::uint32_t targetHeight, const SDL_GPUViewport &viewport, const SequenceWorld3DSlot &slot)
std::expected< StudioEnvironmentData, StudioEnvironmentError > readStudioEnvironment(const std::filesystem::path &path)
std::expected< std::vector< World3DGPUIndexedBatch >, MeshGPUError > buildWorld3DGPUScene(const SequenceWorld3DSlot &slot, MeshGPUCache &cache)
Matrix3D identity3D() noexcept
Matrix2D multiply(const Matrix2D &left, const Matrix2D &right) noexcept
World3DDirectionalLight boardReflection