Monopoly — Portage moderne 0.1.0
Portage moderne C/C++23 du Monopoly de 1999
Loading...
Searching...
No Matches
StudioEnvironmentGPU.cpp
Go to the documentation of this file.
2
3#include <SDL3/SDL.h>
4#include <algorithm>
5#include <array>
6#include <cstring>
7#include <fstream>
8#include <new>
9#include <utility>
10
11namespace monopoly::engine
12{
13 namespace
14 {
15 constexpr std::size_t maximumBytes = 2U * 1024U * 1024U;
16 constexpr std::array<std::uint8_t, 8> magic{'M','S','T','U','D','I','O',0};
17 StudioEnvironmentError invalid(const char* message)
18 { return {StudioEnvironmentErrorCode::InvalidAsset, message}; }
19 StudioEnvironmentError gpuError(StudioEnvironmentErrorCode code, const char* message)
20 {
21 const char* detail = SDL_GetError();
22 return {code, detail && *detail ? detail : message};
23 }
24 std::uint32_t read32(std::span<const std::uint8_t> bytes, std::size_t at)
25 {
26 return static_cast<std::uint32_t>(bytes[at]) |
27 (static_cast<std::uint32_t>(bytes[at+1]) << 8U) |
28 (static_cast<std::uint32_t>(bytes[at+2]) << 16U) |
29 (static_cast<std::uint32_t>(bytes[at+3]) << 24U);
30 }
31 std::size_t payloadSize(std::uint32_t width)
32 {
33 std::size_t result{};
34 for (; width; width >>= 1U) result += static_cast<std::size_t>(width)*width*6U*8U;
35 return result;
36 }
37 bool validPixels(std::span<const std::uint8_t> pixels)
38 {
39 for (std::size_t at = 0; at < pixels.size(); at += 2U)
40 {
41 const auto bits = static_cast<std::uint16_t>(pixels[at] | (pixels[at+1] << 8U));
42 // Reject NaN/Inf, negative values (negative zero allowed), alpha != 1.
43 if ((bits & 0x7C00U) == 0x7C00U ||
44 ((bits & 0x8000U) && (bits & 0x7FFFU)) ||
45 ((at % 8U) == 6U && bits != 0x3C00U)) return false;
46 }
47 return true;
48 }
49 }
50
51 std::expected<StudioEnvironmentData, StudioEnvironmentError>
52 parseStudioEnvironment(std::span<const std::uint8_t> bytes)
53 {
54 if (bytes.size() < 32U || bytes.size() > maximumBytes)
55 return std::unexpected(invalid("studio environment size outside bounded format"));
56 if (!std::equal(magic.begin(), magic.end(), bytes.begin()) || read32(bytes,8U) != 1U)
57 return std::unexpected(invalid("studio environment magic/version mismatch"));
58 const auto width = read32(bytes,12U), levels = read32(bytes,16U);
59 if (!width || width > 128U || (width & (width-1U)) ||
60 read32(bytes,20U) != 6U || read32(bytes,24U) != 1U)
61 return std::unexpected(invalid("studio environment shape/format invalid"));
62 std::uint32_t fullLevels{};
63 for (auto size = width; size; size >>= 1U) ++fullLevels;
64 const auto expected = payloadSize(width);
65 if (levels != fullLevels || levels > 8U || read32(bytes,28U) != expected ||
66 bytes.size() != 32U + expected)
67 return std::unexpected(invalid("studio environment mip/payload size mismatch"));
68 if (!validPixels(bytes.subspan(32U)))
69 return std::unexpected(invalid("studio environment requires finite nonnegative RGB and alpha one"));
70 try
71 {
73 std::vector<std::uint8_t>(bytes.begin()+32, bytes.end())};
74 }
75 catch (const std::bad_alloc&)
76 { return std::unexpected(invalid("studio environment host allocation failed")); }
77 }
78
79 std::expected<StudioEnvironmentData, StudioEnvironmentError>
80 readStudioEnvironment(const std::filesystem::path& path)
81 {
82 try
83 {
84 std::ifstream stream(path, std::ios::binary | std::ios::ate);
85 if (!stream) return std::unexpected(StudioEnvironmentError{
86 StudioEnvironmentErrorCode::FileRead, "could not open studio environment"});
87 const auto size = stream.tellg();
88 if (size < 32 || size > static_cast<std::streamoff>(maximumBytes))
89 return std::unexpected(invalid("studio environment file size outside bounds"));
90 std::vector<std::uint8_t> bytes(static_cast<std::size_t>(size));
91 stream.seekg(0);
92 if (!stream.read(reinterpret_cast<char*>(bytes.data()), size))
93 return std::unexpected(StudioEnvironmentError{
94 StudioEnvironmentErrorCode::FileRead, "could not read studio environment"});
96 }
97 catch (const std::bad_alloc&)
98 { return std::unexpected(invalid("studio environment host allocation failed")); }
99 }
100
105 {
106 if (this != &other)
107 {
108 reset();
109 device_ = std::exchange(other.device_, nullptr);
110 texture_ = std::exchange(other.texture_, nullptr);
111 sampler_ = std::exchange(other.sampler_, nullptr);
112 }
113 return *this;
114 }
116 {
117 if (device_ && texture_) SDL_ReleaseGPUTexture(device_, texture_);
118 if (device_ && sampler_) SDL_ReleaseGPUSampler(device_, sampler_);
119 texture_ = nullptr; sampler_ = nullptr; device_ = nullptr;
120 }
121
122 std::expected<StudioEnvironmentGPU, StudioEnvironmentError>
124 { return uploadInternal(device, data, false); }
125
126 std::expected<StudioEnvironmentGPU, StudioEnvironmentError>
127 StudioEnvironmentGPU::uploadInternal(SDL_GPUDevice* device, const StudioEnvironmentData& data, bool fallback)
128 {
129 std::uint32_t levels{};
130 for (auto width = data.width; width && width <= 128U; width >>= 1U) ++levels;
131 if (!device || !data.width || data.width > 128U || (data.width & (data.width-1U)) ||
132 data.mipCount != levels || data.rgba16f.size() != payloadSize(data.width) ||
133 !validPixels(data.rgba16f))
134 return std::unexpected(invalid("invalid studio environment upload"));
137 const std::uint32_t pixelBytes = fallback ? 4U : 8U;
140 return std::unexpected(StudioEnvironmentError{
141 StudioEnvironmentErrorCode::UnsupportedFormat, "linear HDR cube format unsupported"});
143 result.device_ = device;
145 info.type = SDL_GPU_TEXTURETYPE_CUBE;
146 info.format = format;
147 info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER;
148 info.width = info.height = data.width;
149 info.layer_count_or_depth = 6U;
150 info.num_levels = data.mipCount;
151 info.sample_count = SDL_GPU_SAMPLECOUNT_1;
152 result.texture_ = SDL_CreateGPUTexture(device, &info);
153 if (!result.texture_) return std::unexpected(gpuError(
154 StudioEnvironmentErrorCode::GPUCreation, "could not create HDR cube"));
156 sampler.min_filter = sampler.mag_filter = SDL_GPU_FILTER_LINEAR;
158 sampler.address_mode_u = sampler.address_mode_v = sampler.address_mode_w =
160 sampler.max_lod = static_cast<float>(data.mipCount-1U);
161 result.sampler_ = SDL_CreateGPUSampler(device, &sampler);
162 if (!result.sampler_) return std::unexpected(gpuError(
163 StudioEnvironmentErrorCode::GPUCreation, "could not create HDR cube sampler"));
164 struct Region { std::uint32_t offset, pitch, size, mip, face; std::size_t source; };
165 std::array<Region,48> regions{};
166 std::uint32_t count{}, transferBytes{};
167 std::size_t sourceOffset{};
168 for (std::uint32_t mip = 0, size = data.width; mip < data.mipCount; ++mip, size >>= 1U)
169 for (std::uint32_t face = 0; face < 6U; ++face)
170 {
171 transferBytes = (transferBytes+511U)&~511U;
172 const auto pitch = (size*pixelBytes+255U)&~255U;
175 sourceOffset += static_cast<std::size_t>(size)*size*8U;
176 }
181 if (!transfer) return std::unexpected(gpuError(
182 StudioEnvironmentErrorCode::GPUUpload, "could not create HDR upload buffer"));
183 auto* mapped = static_cast<std::uint8_t*>(SDL_MapGPUTransferBuffer(device, transfer, false));
184 if (!mapped)
185 {
187 return std::unexpected(gpuError(StudioEnvironmentErrorCode::GPUUpload, "could not map HDR upload"));
188 }
189 std::memset(mapped,0,transferBytes);
190 for (std::uint32_t i = 0; i < count; ++i)
191 for (std::uint32_t row = 0; row < regions[i].size; ++row)
192 {
193 if (fallback) mapped[regions[i].offset+3U] = 255U;
194 else std::memcpy(mapped+regions[i].offset+row*regions[i].pitch,
195 data.rgba16f.data()+regions[i].source+row*regions[i].size*8U, regions[i].size*8U);
196 }
198 auto* command = SDL_AcquireGPUCommandBuffer(device);
199 auto* copy = command ? SDL_BeginGPUCopyPass(command) : nullptr;
200 if (!copy)
201 {
202 if (command) SDL_CancelGPUCommandBuffer(command);
204 return std::unexpected(gpuError(StudioEnvironmentErrorCode::GPUUpload, "could not begin HDR upload"));
205 }
206 for (std::uint32_t i = 0; i < count; ++i)
207 {
208 const auto& region = regions[i];
210 from.transfer_buffer = transfer; from.offset = region.offset;
211 from.pixels_per_row = region.pitch/pixelBytes; from.rows_per_layer = region.size;
213 to.texture = result.texture_; to.layer = region.face; to.mip_level = region.mip;
214 to.w = to.h = region.size; to.d = 1U;
215 SDL_UploadToGPUTexture(copy, &from, &to, false);
216 }
218 const bool submitted = SDL_SubmitGPUCommandBuffer(command);
220 if (!submitted) return std::unexpected(gpuError(
221 StudioEnvironmentErrorCode::GPUUpload, "could not submit HDR upload"));
222 return result;
223 }
224
225 std::expected<StudioEnvironmentGPU, StudioEnvironmentError>
227 {
228 StudioEnvironmentData black{1U,1U,std::vector<std::uint8_t>(48U,0U)};
229 for (std::size_t face = 0; face < 6U; ++face) black.rgba16f[face*8U+7U] = 0x3CU;
230 return uploadInternal(device,black,true);
231 }
232}
std::size_t count
std::vector< std::uint8_t > data
std::size_t offset
std::vector< std::byte > bytes
std::shared_ptr< const data::HmdTextureImage > source
std::uint32_t transferBytes
std::uint32_t width
unsigned index
std::uint16_t pitch
std::string path
std::size_t at
CardTag other
SDL_GPUShaderFormat format
static std::expected< StudioEnvironmentGPU, StudioEnvironmentError > upload(SDL_GPUDevice *, const StudioEnvironmentData &)
static std::expected< StudioEnvironmentGPU, StudioEnvironmentError > blackFallback(SDL_GPUDevice *)
SDL_GPUSampler * sampler() const noexcept
StudioEnvironmentGPU & operator=(const StudioEnvironmentGPU &)=delete
std::expected< StudioEnvironmentData, StudioEnvironmentError > readStudioEnvironment(const std::filesystem::path &path)
std::expected< StudioEnvironmentData, StudioEnvironmentError > parseStudioEnvironment(std::span< const std::uint8_t > bytes)
std::size_t size()