Engine²
Open-source game engine written in C++.
Loading...
Searching...
No Matches
Texture.hpp
Go to the documentation of this file.
1#pragma once
2
5#include "resource/Image.hpp"
7#include "utils/webgpu.hpp"
8#include <array>
9#include <bit>
10#include <cstddef>
11#include <cstdint>
12#include <cstring>
13#include <functional>
14#include <glm/gtc/packing.hpp>
15#include <glm/vec2.hpp>
16#include <glm/vec4.hpp>
17#include <vector>
18
19namespace Graphic::Resource {
20
22 wgpu::Buffer buffer;
24 uint32_t bytesPerRow;
25 wgpu::TextureFormat format;
26 bool done = false;
27};
28
49static void TextureRetrieveCallback(WGPUMapAsyncStatus status, WGPUStringView message, void *userdata1, void *userdata2)
50{
51 auto data = static_cast<CallbackData *>(userdata1);
52 if (status != wgpu::MapAsyncStatus::Success)
53 {
54 Log::Error(fmt::format("Failed to map buffer: {}", std::string_view(message.data, message.length)));
55 data->done = true;
56 return;
57 }
58 auto &buf = data->buffer;
59 auto mapped = static_cast<uint8_t *>(buf.getMappedRange(0, buf.getSize()));
60 uint32_t size = (data->bytesPerRow / Graphic::Utils::GetBytesPerPixel(data->format)) * data->data.height;
61 for (size_t i = 0; i < size; i++)
62 {
63 if (i % (data->bytesPerRow / Graphic::Utils::GetBytesPerPixel(data->format)) >= data->data.width)
64 {
65 continue; // Skip padding bytes
66 }
67 glm::u8vec4 pixel;
68 // Here we assume the texture format is RGBA8Unorm (4 bytes per pixel)
69 switch (data->format)
70 {
71 case wgpu::TextureFormat::RGBA8UnormSrgb:
72 case wgpu::TextureFormat::RGBA8Unorm:
73 pixel.r = mapped[i * 4 + 0];
74 pixel.g = mapped[i * 4 + 1];
75 pixel.b = mapped[i * 4 + 2];
76 pixel.a = mapped[i * 4 + 3];
77 break;
78 case wgpu::TextureFormat::BGRA8UnormSrgb:
79 case wgpu::TextureFormat::BGRA8Unorm:
80 pixel.b = mapped[i * 4 + 0];
81 pixel.g = mapped[i * 4 + 1];
82 pixel.r = mapped[i * 4 + 2];
83 pixel.a = mapped[i * 4 + 3];
84 break;
85 case wgpu::TextureFormat::RGBA16Float: {
86 auto r = std::bit_cast<uint16_t>(std::array<uint8_t, 2>{mapped[i * 8 + 0], mapped[i * 8 + 1]});
87 auto g = std::bit_cast<uint16_t>(std::array<uint8_t, 2>{mapped[i * 8 + 2], mapped[i * 8 + 3]});
88 auto b = std::bit_cast<uint16_t>(std::array<uint8_t, 2>{mapped[i * 8 + 4], mapped[i * 8 + 5]});
89 auto a = std::bit_cast<uint16_t>(std::array<uint8_t, 2>{mapped[i * 8 + 6], mapped[i * 8 + 7]});
90 pixel.r = static_cast<uint8_t>(std::clamp(glm::unpackHalf1x16(r), 0.0f, 1.0f) * 255.0f);
91 pixel.g = static_cast<uint8_t>(std::clamp(glm::unpackHalf1x16(g), 0.0f, 1.0f) * 255.0f);
92 pixel.b = static_cast<uint8_t>(std::clamp(glm::unpackHalf1x16(b), 0.0f, 1.0f) * 255.0f);
93 pixel.a = static_cast<uint8_t>(std::clamp(glm::unpackHalf1x16(a), 0.0f, 1.0f) * 255.0f);
94 break;
95 }
96 case wgpu::TextureFormat::Depth32Float: {
97 auto depth = std::bit_cast<float>(
98 std::array<uint8_t, 4>{mapped[i * 4 + 0], mapped[i * 4 + 1], mapped[i * 4 + 2], mapped[i * 4 + 3]});
99 auto depthByte = static_cast<uint8_t>(std::clamp(depth, 0.0f, 1.0f) * 255.0f);
100 pixel = glm::u8vec4(depthByte, depthByte, depthByte, 255);
101 break;
102 }
103 default: throw Exception::UnsupportedTextureFormatError("Texture format not supported for retrieval.");
104 }
105 data->data.pixels.push_back(pixel);
106 }
107 buf.unmap();
108 data->done = true;
109};
110class Texture {
111 public:
112 Texture(std::string_view name, wgpu::Texture texture, bool ownsResources = true)
113 : _webgpuTexture(texture), _defaultView(nullptr), _name(std::string(name)), _ownsResources(ownsResources)
114 {
115 _defaultView = _webgpuTexture.createView();
116 }
117
118 Texture(const Context &context, const wgpu::TextureDescriptor &descriptor)
119 : Texture(std::string(descriptor.label.data, descriptor.label.length),
120 context.deviceContext.GetDevice()->createTexture(descriptor))
121 {
122 }
123
124 Texture(const Context &context, std::string_view name, const Image &image)
125 : Texture(context, _BuildDescriptor(name, image))
126 {
127 Write(context, image);
128 }
129
130 Texture(const Context &context, std::string_view name, const glm::uvec2 &size,
131 const std::function<glm::u8vec4(glm::uvec2 pos)> &callback)
132 : Texture(context, name, Image(size, callback))
133 {
134 }
135
137 {
138 if (_defaultView != nullptr)
139 _defaultView.release();
140
141 if (_ownsResources && _webgpuTexture != nullptr)
142 _webgpuTexture.release();
143 }
144
145 Texture(const Texture &) = delete;
146 Texture &operator=(const Texture &) = delete;
147
148 Texture(Texture &&other) noexcept
149 : _webgpuTexture(std::move(other._webgpuTexture)), _defaultView(std::move(other._defaultView)),
150 _name(std::move(other._name)), _ownsResources(other._ownsResources)
151 {
152 other._webgpuTexture = nullptr;
153 other._defaultView = nullptr;
154 other._name.clear();
155 other._ownsResources = false;
156 }
157
158 Texture &operator=(Texture &&other) noexcept
159 {
160 if (this != &other)
161 {
162 if (_defaultView != nullptr)
163 _defaultView.release();
164
165 if (_ownsResources && _webgpuTexture != nullptr)
166 {
167 _webgpuTexture.release();
168 }
169
170 _webgpuTexture = std::move(other._webgpuTexture);
171 _defaultView = std::move(other._defaultView);
172 _name = std::move(other._name);
173 _ownsResources = other._ownsResources;
174
175 other._webgpuTexture = nullptr;
176 other._defaultView = nullptr;
177 other._name.clear();
178 other._ownsResources = false;
179 }
180 return *this;
181 }
182
183 inline glm::uvec2 GetSize() const { return glm::uvec2{_webgpuTexture.getWidth(), _webgpuTexture.getHeight()}; }
184
185 // We assume the image is correctly formatted (width * height = pixels.size())
186 void Write(const Context &context, const Image &image)
187 {
188 if (image.width != this->_webgpuTexture.getWidth() || image.height != this->_webgpuTexture.getHeight())
189 {
190 Log::Warning("Image data size does not match texture size.");
191 }
192
193 wgpu::Extent3D textureSize = {this->_webgpuTexture.getWidth(), this->_webgpuTexture.getHeight(), 1};
194 wgpu::TexelCopyTextureInfo destination;
195 destination.texture = this->_webgpuTexture;
196 destination.mipLevel = 0;
197 destination.origin = {0, 0, 0};
198 destination.aspect = wgpu::TextureAspect::All;
199
200 wgpu::TexelCopyBufferLayout source;
201 source.offset = 0;
202 source.bytesPerRow = image.channels * textureSize.width;
203 source.rowsPerImage = textureSize.height;
204
205 const wgpu::Queue &queue = context.queue.value();
206 const uint32_t rowBytes = source.bytesPerRow;
207 const uint32_t alignedRowBytes = (rowBytes + 255u) & ~255u;
208 if (alignedRowBytes == rowBytes)
209 {
210 queue.writeTexture(destination, image.pixels.data(), rowBytes * source.rowsPerImage, source, textureSize);
211 return;
212 }
213
214 std::vector<uint8_t> padded(alignedRowBytes * textureSize.height, 0u);
215 const auto *const src = reinterpret_cast<const std::byte *>(image.pixels.data());
216 for (uint32_t row = 0; row < textureSize.height; ++row)
217 {
218 std::memcpy(padded.data() + row * alignedRowBytes, src + row * rowBytes, rowBytes);
219 }
220
221 source.bytesPerRow = alignedRowBytes;
222 queue.writeTexture(destination, padded.data(), alignedRowBytes * source.rowsPerImage, source, textureSize);
223 }
224
234 Image RetrieveImage(const Context &context) const
235 {
236 const wgpu::Queue &queue = context.queue.value();
237
238 wgpu::Extent3D copySize(_webgpuTexture.getWidth(), _webgpuTexture.getHeight(), 1);
239
240 wgpu::CommandEncoder encoder = context.deviceContext.GetDevice()->createCommandEncoder();
241
242 const uint32_t bytesPerRow = (copySize.width * _GetBytesPerPixel() + 255) / 256 * 256;
243 wgpu::BufferDescriptor bufDesc(wgpu::Default);
244 bufDesc.size = copySize.height * bytesPerRow;
245 bufDesc.usage = wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::MapRead;
246 bufDesc.label = wgpu::StringView("Depth Readback Buffer");
247 wgpu::Buffer readbackBuffer = context.deviceContext.GetDevice()->createBuffer(bufDesc);
248
249 wgpu::TexelCopyTextureInfo srcView(wgpu::Default);
250 srcView.texture = _webgpuTexture;
251 srcView.mipLevel = 0;
252 srcView.origin = {0, 0, 0};
253 if (_webgpuTexture.getFormat() == wgpu::TextureFormat::Depth24Plus ||
254 _webgpuTexture.getFormat() == wgpu::TextureFormat::Depth24PlusStencil8 ||
255 _webgpuTexture.getFormat() == wgpu::TextureFormat::Depth32Float ||
256 _webgpuTexture.getFormat() == wgpu::TextureFormat::Depth32FloatStencil8)
257 {
258 srcView.aspect = wgpu::TextureAspect::DepthOnly;
259 }
260 else
261 srcView.aspect = wgpu::TextureAspect::All;
262
263 wgpu::TexelCopyBufferInfo dstView(wgpu::Default);
264 dstView.buffer = readbackBuffer;
265 dstView.layout.offset = 0;
266 dstView.layout.bytesPerRow = bytesPerRow;
267 dstView.layout.rowsPerImage = copySize.height;
268
269 encoder.copyTextureToBuffer(srcView, dstView, copySize);
270 auto cmd = encoder.finish();
271 auto cmdName = fmt::format("{} Readback Command", _name);
272 queue.submit(1, &cmd);
273 cmd.release();
274
275 CallbackData cbData = {readbackBuffer, {}, bytesPerRow, _webgpuTexture.getFormat(), false};
276 cbData.data.width = copySize.width;
277 cbData.data.height = copySize.height;
278 cbData.data.channels = 4;
279
280 wgpu::BufferMapCallbackInfo cbInfo(wgpu::Default);
281 cbInfo.mode = wgpu::CallbackMode::AllowSpontaneous;
282 cbInfo.callback = TextureRetrieveCallback;
283 cbInfo.userdata1 = &cbData;
284 cbInfo.userdata2 = nullptr;
285 readbackBuffer.mapAsync(wgpu::MapMode::Read, 0, readbackBuffer.getSize(), cbInfo);
286 while (!cbData.done)
287 {
288 context.deviceContext.GetDevice()->poll(false, nullptr);
289 std::this_thread::sleep_for(std::chrono::milliseconds(100));
290 }
291 return cbData.data;
292 }
293
294 wgpu::TextureView GetDefaultView() const { return _defaultView; }
295
296 inline wgpu::TextureView CreateView(const wgpu::TextureViewDescriptor &descriptor) const
297 {
298 return _webgpuTexture.createView(descriptor);
299 }
300
302
303 void TakeOwnership() { _ownsResources = true; }
304
305 bool OwnsResources() const { return _ownsResources; }
306
307 private:
308 Texture(void) = default;
309
310 static wgpu::TextureDescriptor _BuildDescriptor(std::string_view name, const Image &image)
311 {
312 wgpu::TextureDescriptor textureDesc(wgpu::Default);
313 textureDesc.label = wgpu::StringView(name);
314 textureDesc.size = {image.width, image.height, 1};
315 textureDesc.dimension = wgpu::TextureDimension::_2D;
316 textureDesc.mipLevelCount = 1;
317 textureDesc.sampleCount = 1;
318 textureDesc.format = wgpu::TextureFormat::RGBA8UnormSrgb;
319 textureDesc.usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::RenderAttachment |
320 wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst;
321 textureDesc.viewFormats = nullptr;
322 textureDesc.viewFormatCount = 0;
323 return textureDesc;
324 }
325
326 uint32_t _GetBytesPerPixel() const { return Utils::GetBytesPerPixel(_webgpuTexture.getFormat()); }
327
328 wgpu::Texture _webgpuTexture;
329 wgpu::TextureView _defaultView;
330 std::string _name;
331 bool _ownsResources = true;
332};
333} // namespace Graphic::Resource
Definition UnsupportedTextureFormatError.hpp:7
Definition Context.hpp:8
DeviceContext deviceContext
Definition Context.hpp:43
std::optional< wgpu::Queue > queue
Definition Context.hpp:44
Texture(const Context &context, const wgpu::TextureDescriptor &descriptor)
Definition Texture.hpp:118
wgpu::TextureView GetDefaultView() const
Definition Texture.hpp:294
Texture & operator=(Texture &&other) noexcept
Definition Texture.hpp:158
wgpu::Texture _webgpuTexture
Definition Texture.hpp:328
Texture & operator=(const Texture &)=delete
static wgpu::TextureDescriptor _BuildDescriptor(std::string_view name, const Image &image)
Definition Texture.hpp:310
bool OwnsResources() const
Definition Texture.hpp:305
std::string _name
Definition Texture.hpp:330
void TakeOwnership()
Definition Texture.hpp:303
void Write(const Context &context, const Image &image)
Definition Texture.hpp:186
wgpu::TextureView CreateView(const wgpu::TextureViewDescriptor &descriptor) const
Definition Texture.hpp:296
Texture(std::string_view name, wgpu::Texture texture, bool ownsResources=true)
Definition Texture.hpp:112
Texture(const Texture &)=delete
uint32_t _GetBytesPerPixel() const
Definition Texture.hpp:326
Texture(Texture &&other) noexcept
Definition Texture.hpp:148
bool _ownsResources
Definition Texture.hpp:331
Texture(const Context &context, std::string_view name, const glm::uvec2 &size, const std::function< glm::u8vec4(glm::uvec2 pos)> &callback)
Definition Texture.hpp:130
Image RetrieveImage(const Context &context) const
Reads back the GPU texture and returns it as an Image.
Definition Texture.hpp:234
Texture(const Context &context, std::string_view name, const Image &image)
Definition Texture.hpp:124
glm::uvec2 GetSize() const
Definition Texture.hpp:183
void ReleaseOwnership()
Definition Texture.hpp:301
~Texture()
Definition Texture.hpp:136
wgpu::TextureView _defaultView
Definition Texture.hpp:329
Definition AGPUBuffer.hpp:6
static void TextureRetrieveCallback(WGPUMapAsyncStatus status, WGPUStringView message, void *userdata1, void *userdata2)
Callback invoked when a readback buffer mapping completes; converts mapped texture data into RGBA8 pi...
Definition Texture.hpp:49
uint32_t GetBytesPerPixel(wgpu::TextureFormat format)
Determines the number of bytes per pixel for a given wgpu texture format.
Definition GetBytesPerPixel.cpp:13
void Warning(const T &msg) noexcept
Definition Logger.hpp:49
void Error(const T &msg) noexcept
Definition Logger.hpp:51
constexpr DefaultFlag Default
Definition webgpu.hpp:78
StringView(const std::string_view &cpp)
Definition webgpu.hpp:618
Extent3D(uint32_t width, uint32_t height, uint32_t depthOrArrayLayers)
Definition webgpu.hpp:643
Definition Texture.hpp:21
bool done
Definition Texture.hpp:26
Image data
Definition Texture.hpp:23
uint32_t bytesPerRow
Definition Texture.hpp:24
wgpu::TextureFormat format
Definition Texture.hpp:25
wgpu::Buffer buffer
Definition Texture.hpp:22
auto & GetDevice()
Definition DeviceContext.hpp:13
Definition Image.hpp:18
uint32_t height
Definition Image.hpp:20
uint32_t width
Definition Image.hpp:19
int channels
Definition Image.hpp:21
std::vector< glm::u8vec4 > pixels
Definition Image.hpp:22