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