#include "common.hpp" #include "../internal.hpp" #include "../webgpu/gpu.hpp" #include "aurora/aurora.h" #include "texture.hpp" #include "texture_convert.hpp" #include "../gx/gx_fmt.hpp" #include #include #include #include #include #include #include #include namespace aurora::gfx { using webgpu::g_device; using webgpu::g_queue; namespace { Module Log("aurora::gfx"); constexpr u32 div_ceil(u32 value, u32 divisor) noexcept { return (value + divisor - 1) / divisor; } uint32_t clamp_mip_count(uint32_t width, uint32_t height, uint32_t mips, const char* label) noexcept { const uint32_t maxMips = max_texture_mip_count(width, height); const uint32_t safeMips = std::clamp(mips, 1u, maxMips); if (safeMips != mips) { Log.warn("{}: clamping mip count {} to {} for {}x{} texture", label, mips, safeMips, width, height); } return safeMips; } wgpu::Extent3D physical_size(wgpu::Extent3D size, TextureFormatInfo info) { const uint32_t width = ((size.width + info.blockWidth - 1) / info.blockWidth) * info.blockWidth; const uint32_t height = ((size.height + info.blockHeight - 1) / info.blockHeight) * info.blockHeight; return {.width = width, .height = height, .depthOrArrayLayers = size.depthOrArrayLayers}; } } // namespace TextureFormatInfo format_info(wgpu::TextureFormat format) noexcept { switch (format) { DEFAULT_FATAL("unimplemented texture format {}", magic_enum::enum_name(format)); case wgpu::TextureFormat::R8Unorm: return {1, 1, 1, false}; case wgpu::TextureFormat::RG8Unorm: case wgpu::TextureFormat::R16Sint: return {1, 1, 2, false}; case wgpu::TextureFormat::RGBA8Unorm: case wgpu::TextureFormat::BGRA8Unorm: case wgpu::TextureFormat::R32Float: return {1, 1, 4, false}; case wgpu::TextureFormat::BC1RGBAUnorm: return {4, 4, 8, true}; case wgpu::TextureFormat::BC3RGBAUnorm: case wgpu::TextureFormat::BC5RGUnorm: case wgpu::TextureFormat::BC7RGBAUnorm: return {4, 4, 16, true}; } } uint64_t calc_texture_size(wgpu::TextureFormat format, u32 width, u32 height, u32 mips) noexcept { const auto info = format_info(format); uint64_t total = 0; for (uint32_t mip = 0; mip < mips; ++mip) { const uint32_t mipWidth = std::max(width >> mip, 1u); const uint32_t mipHeight = std::max(height >> mip, 1u); const uint64_t widthBlocks = div_ceil(mipWidth, info.blockWidth); const uint64_t heightBlocks = div_ceil(mipHeight, info.blockHeight); const uint64_t mipBytes = widthBlocks * heightBlocks * info.blockSize; total += mipBytes; } return total; } TextureHandle new_static_texture_2d(uint32_t width, uint32_t height, uint32_t mips, u32 format, ArrayRef data, bool tlut, const char* label) noexcept { ZoneScoped; mips = clamp_mip_count(width, height, mips, label); auto handle = new_dynamic_texture_2d(width, height, mips, format, label); auto& ref = *handle; ConvertedTexture converted; if (ref.gxFormat != InvalidTextureFormat) { if (tlut) { CHECK(ref.size.height == 1, "new_static_texture_2d[{}]: expected tlut height 1, got {}", label, ref.size.height); CHECK(ref.mipCount == 1, "new_static_texture_2d[{}]: expected tlut mipCount 1, got {}", label, ref.mipCount); converted = convert_tlut(ref.gxFormat, ref.size.width, data); } else { converted = convert_texture(ref.gxFormat, ref.size.width, ref.size.height, ref.mipCount, data); } if (!converted.data.empty()) { data = converted.data; ref.hasArbitraryMips = converted.hasArbitraryMips; } } uint32_t offset = 0; for (uint32_t mip = 0; mip < mips; ++mip) { const wgpu::Extent3D mipSize{ .width = std::max(ref.size.width >> mip, 1u), .height = std::max(ref.size.height >> mip, 1u), .depthOrArrayLayers = ref.size.depthOrArrayLayers, }; const auto info = format_info(ref.format); const auto physicalSize = physical_size(mipSize, info); const uint32_t widthBlocks = physicalSize.width / info.blockWidth; const uint32_t heightBlocks = physicalSize.height / info.blockHeight; const uint32_t bytesPerRow = widthBlocks * info.blockSize; const uint32_t dataSize = bytesPerRow * heightBlocks * mipSize.depthOrArrayLayers; if (offset > data.size() || dataSize > data.size() - offset) { Log.warn("new_static_texture_2d[{}]: stopping at mip {}: expected at least {} bytes, got {}", label, mip, static_cast(offset) + dataSize, data.size()); return handle; } const wgpu::TexelCopyTextureInfo dstView{ .texture = ref.texture, .mipLevel = mip, }; if constexpr (UseTextureBuffer) { const auto range = push_texture_data(data.data() + offset, dataSize, bytesPerRow, heightBlocks); const wgpu::TexelCopyBufferLayout dataLayout{ .offset = range.offset, .bytesPerRow = bytesPerRow, .rowsPerImage = heightBlocks, }; g_textureUploads.emplace_back(dataLayout, std::move(dstView), physicalSize); } else { const wgpu::TexelCopyBufferLayout dataLayout{ .bytesPerRow = bytesPerRow, .rowsPerImage = heightBlocks, }; g_queue.WriteTexture(&dstView, data.data() + offset, dataSize, &dataLayout, &physicalSize); } offset += dataSize; } if (data.size() != UINT32_MAX && offset < data.size()) { Log.warn("new_static_texture_2d[{}]: texture used {} bytes, but given {} bytes", label, offset, data.size()); } return handle; } TextureHandle new_dynamic_texture_2d(uint32_t width, uint32_t height, uint32_t mips, u32 gxFormat, const char* label) noexcept { ZoneScopedS(3); mips = clamp_mip_count(width, height, mips, label); const auto wgpuFormat = to_wgpu(gxFormat); const wgpu::Extent3D size{ .width = width, .height = height, .depthOrArrayLayers = 1, }; const wgpu::TextureDescriptor textureDescriptor{ .label = label, .usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopyDst, .dimension = wgpu::TextureDimension::e2D, .size = size, .format = wgpuFormat, .mipLevelCount = mips, .sampleCount = 1, }; auto texture = g_device.CreateTexture(&textureDescriptor); const auto viewLabel = fmt::format("{} view", label); wgpu::TextureViewDescriptor textureViewDescriptor{ .label = viewLabel.c_str(), .format = wgpuFormat, .dimension = wgpu::TextureViewDimension::e2D, .mipLevelCount = mips, }; auto textureView = texture.CreateView(&textureViewDescriptor); return std::make_shared(std::move(texture), std::move(textureView), wgpu::TextureView{}, size, wgpuFormat, mips, gxFormat); } TextureHandle new_render_texture(uint32_t width, uint32_t height, u32 gxFormat, const char* label) noexcept { ZoneScoped; const auto wgpuFormat = webgpu::g_graphicsConfig.surfaceConfiguration.format; const wgpu::Extent3D size{ .width = width, .height = height, .depthOrArrayLayers = 1, }; const wgpu::TextureDescriptor textureDescriptor{ .label = label, .usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::CopyDst | wgpu::TextureUsage::RenderAttachment, .dimension = wgpu::TextureDimension::e2D, .size = size, .format = wgpuFormat, .mipLevelCount = 1, .sampleCount = 1, }; auto texture = g_device.CreateTexture(&textureDescriptor); // Create texture view for color attachments const auto viewLabel = fmt::format("{} view", label); wgpu::TextureViewDescriptor textureViewDescriptor{ .label = viewLabel.c_str(), .format = wgpuFormat, .dimension = wgpu::TextureViewDimension::e2D, }; auto attachmentTextureView = texture.CreateView(&textureViewDescriptor); wgpu::TextureView sampleTextureView = attachmentTextureView; return std::make_shared(std::move(texture), std::move(sampleTextureView), std::move(attachmentTextureView), size, wgpuFormat, 1, gxFormat); } TextureHandle new_conv_texture(uint32_t width, uint32_t height, u32 gxFormat, const char* label) noexcept { ZoneScoped; const auto wgpuFormat = to_wgpu(gxFormat); const wgpu::Extent3D size{ .width = width, .height = height, .depthOrArrayLayers = 1, }; const wgpu::TextureDescriptor textureDescriptor{ .label = label, .usage = wgpu::TextureUsage::TextureBinding | wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment, .dimension = wgpu::TextureDimension::e2D, .size = size, .format = wgpuFormat, .mipLevelCount = 1, .sampleCount = 1, }; auto texture = g_device.CreateTexture(&textureDescriptor); // Create texture view for color attachments const auto viewLabel = fmt::format("{} view", label); wgpu::TextureViewDescriptor textureViewDescriptor{ .label = viewLabel.c_str(), .format = wgpuFormat, .dimension = wgpu::TextureViewDimension::e2D, }; auto attachmentTextureView = texture.CreateView(&textureViewDescriptor); wgpu::TextureView sampleTextureView = attachmentTextureView; return std::make_shared(std::move(texture), std::move(sampleTextureView), std::move(attachmentTextureView), size, wgpuFormat, 1, gxFormat); } void write_texture(TextureRef& ref, ArrayRef data) noexcept { ZoneScoped; ConvertedTexture converted; if (ref.gxFormat != InvalidTextureFormat) { converted = convert_texture(ref.gxFormat, ref.size.width, ref.size.height, ref.mipCount, data); ref.hasArbitraryMips = converted.hasArbitraryMips; if (!converted.data.empty()) { data = converted.data; } } uint32_t offset = 0; for (uint32_t mip = 0; mip < ref.mipCount; ++mip) { const wgpu::Extent3D mipSize{ .width = std::max(ref.size.width >> mip, 1u), .height = std::max(ref.size.height >> mip, 1u), .depthOrArrayLayers = ref.size.depthOrArrayLayers, }; const auto info = format_info(ref.format); const auto physicalSize = physical_size(mipSize, info); const uint32_t widthBlocks = physicalSize.width / info.blockWidth; const uint32_t heightBlocks = physicalSize.height / info.blockHeight; const uint32_t bytesPerRow = widthBlocks * info.blockSize; const uint32_t dataSize = bytesPerRow * heightBlocks * mipSize.depthOrArrayLayers; if (offset > data.size() || dataSize > data.size() - offset) { Log.warn("write_texture: stopping at mip {}: expected at least {} bytes, got {}", mip, static_cast(offset) + dataSize, data.size()); return; } const wgpu::TexelCopyTextureInfo dstView{ .texture = ref.texture, .mipLevel = mip, }; if constexpr (UseTextureBuffer) { const auto range = push_texture_data(data.data() + offset, dataSize, bytesPerRow, heightBlocks); const wgpu::TexelCopyBufferLayout dataLayout{ .offset = range.offset, .bytesPerRow = bytesPerRow, .rowsPerImage = heightBlocks, }; g_textureUploads.emplace_back(dataLayout, std::move(dstView), physicalSize); } else { const wgpu::TexelCopyBufferLayout dataLayout{ .bytesPerRow = bytesPerRow, .rowsPerImage = heightBlocks, }; g_queue.WriteTexture(&dstView, data.data() + offset, dataSize, &dataLayout, &physicalSize); } offset += dataSize; } if (data.size() != UINT32_MAX && offset < data.size()) { Log.warn("write_texture: texture used {} bytes, but given {} bytes", offset, data.size()); } } } // namespace aurora::gfx