// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin // SPDX-License-Identifier: CC-BY-NC-ND-4.0 #include "gpu_texture.h" #include "gpu_device.h" #include "image.h" #include "common/align.h" #include "common/assert.h" #include "common/bitutils.h" #include "common/error.h" #include "common/string_util.h" GPUTexture::GPUTexture(u16 width, u16 height, u8 layers, u8 levels, u8 samples, Type type, Format format, Flags flags) : m_width(width), m_height(height), m_layers(layers), m_levels(levels), m_samples(samples), m_type(type), m_format(format), m_flags(flags) { GPUDevice::s_total_vram_usage += GetVRAMUsage(); } GPUTexture::~GPUTexture() { GPUDevice::s_total_vram_usage -= GetVRAMUsage(); } const char* GPUTexture::GetFormatName(Format format) { static constexpr const std::array(Format::MaxCount)> format_names = {{ "Unknown", // Unknown "RGBA8", // RGBA8 "BGRA8", // BGRA8 "RGB565", // RGB565 "RGB5A1", // RGB5A1 "A1BGR5", // A1BGR5 "R8", // R8 "D16", // D16 "D24S8", // D24S8 "D32F", // D32F "D32FS8S", // D32FS8 "R16", // R16 "R16I", // R16I "R16U", // R16U "R16F", // R16F "R32I", // R32I "R32U", // R32U "R32F", // R32F "RG8", // RG8 "RG16", // RG16 "RG16F", // RG16F "RG32F", // RG32F "RGBA16", // RGBA16 "RGBA16F", // RGBA16F "RGBA32F", // RGBA32F "RGB10A2", // RGB10A2 "BC1", // BC1 "BC2", // BC2 "BC3", // BC3 "BC7", // BC7 }}; return format_names[static_cast(format)]; } u32 GPUTexture::GetBlockSize() const { return GetBlockSize(m_format); } u32 GPUTexture::GetBlockSize(Format format) { if (format >= Format::BC1 && format <= Format::BC7) return COMPRESSED_TEXTURE_BLOCK_SIZE; else return 1; } u32 GPUTexture::CalcUploadPitch(Format format, u32 width) { // convert to blocks if (format >= Format::BC1 && format <= Format::BC7) width = Common::AlignUpPow2(width, COMPRESSED_TEXTURE_BLOCK_SIZE) / COMPRESSED_TEXTURE_BLOCK_SIZE; return width * GetPixelSize(format); } u32 GPUTexture::CalcUploadPitch(u32 width) const { return CalcUploadPitch(m_format, width); } u32 GPUTexture::CalcUploadRowLengthFromPitch(u32 pitch) const { return CalcUploadRowLengthFromPitch(m_format, pitch); } u32 GPUTexture::CalcUploadRowLengthFromPitch(Format format, u32 pitch) { const u32 pixel_size = GetPixelSize(format); if (IsCompressedFormat(format)) return (Common::AlignUpPow2(pitch, pixel_size) / pixel_size) * COMPRESSED_TEXTURE_BLOCK_SIZE; else return pitch / pixel_size; } u32 GPUTexture::CalcUploadSize(u32 height, u32 pitch) const { return CalcUploadSize(m_format, height, pitch); } u32 GPUTexture::CalcUploadSize(Format format, u32 height, u32 pitch) { const u32 block_size = GetBlockSize(format); return pitch * ((static_cast(height) + (block_size - 1)) / block_size); } bool GPUTexture::IsCompressedFormat(Format format) { return (format >= Format::BC1); } bool GPUTexture::IsCompressedFormat() const { return IsCompressedFormat(m_format); } u32 GPUTexture::GetFullMipmapCount(u32 width, u32 height) { const u32 max_dim = Common::PreviousPow2(std::max(width, height)); return (std::countr_zero(max_dim) + 1); } void GPUTexture::CopyTextureDataForUpload(u32 width, u32 height, Format format, void* dst, u32 dst_pitch, const void* src, u32 src_pitch) { if (IsCompressedFormat(format)) { const u32 blocks_wide = Common::AlignUpPow2(width, COMPRESSED_TEXTURE_BLOCK_SIZE) / COMPRESSED_TEXTURE_BLOCK_SIZE; const u32 blocks_high = Common::AlignUpPow2(height, COMPRESSED_TEXTURE_BLOCK_SIZE) / COMPRESSED_TEXTURE_BLOCK_SIZE; const u32 block_size = GetPixelSize(format); StringUtil::StrideMemCpy(dst, dst_pitch, src, src_pitch, block_size * blocks_wide, blocks_high); } else { StringUtil::StrideMemCpy(dst, dst_pitch, src, src_pitch, width * GetPixelSize(format), height); } } GPUTexture::Format GPUTexture::GetTextureFormatForImageFormat(ImageFormat format) { static constexpr const std::array mapping = { Format::Unknown, // None Format::RGBA8, // RGBA8 Format::BGRA8, // BGRA8 Format::RGB565, // RGB565 Format::RGB5A1, // RGB5A1 Format::A1BGR5, // A1BGR5 Format::Unknown, // BGR8 Format::BC1, // BC1 Format::BC2, // BC2 Format::BC3, // BC3 Format::BC7, // BC7 }; static_assert(mapping.size() == static_cast(ImageFormat::MaxCount)); return mapping[static_cast(format)]; } ImageFormat GPUTexture::GetImageFormatForTextureFormat(Format format) { static constexpr const std::array mapping = { ImageFormat::None, // Unknown ImageFormat::RGBA8, // RGBA8 ImageFormat::BGRA8, // BGRA8 ImageFormat::RGB565, // RGB565 ImageFormat::RGB5A1, // RGB5A1 ImageFormat::A1BGR5, // A1BGR5 ImageFormat::None, // R8 ImageFormat::None, // D16 ImageFormat::None, // D24S8 ImageFormat::None, // D32F ImageFormat::None, // D32FS8 ImageFormat::None, // R16 ImageFormat::None, // R16I ImageFormat::None, // R16U ImageFormat::None, // R16F ImageFormat::None, // R32I ImageFormat::None, // R32U ImageFormat::None, // R32F ImageFormat::None, // RG8 ImageFormat::None, // RG16 ImageFormat::None, // RG16F ImageFormat::None, // RG32F ImageFormat::None, // RGBA16 ImageFormat::None, // RGBA16F ImageFormat::None, // RGBA32F ImageFormat::None, // RGB10A2 ImageFormat::BC1, // BC1 ImageFormat::BC2, // BC2 ImageFormat::BC3, // BC3 ImageFormat::BC7, // BC7 }; static_assert(mapping.size() == static_cast(Format::MaxCount)); return mapping[static_cast(format)]; } std::array GPUTexture::GetUNormClearColor() const { return GPUDevice::RGBA8ToFloat(m_clear_value.color); } size_t GPUTexture::GetVRAMUsage() const { const size_t ps = GetPixelSize(m_format) * m_layers * m_samples; size_t mem; // Max width/height is 65535, 65535*65535 as u32 is okay. if (IsCompressedFormat()) { #define COMPRESSED_SIZE(width, height) \ (static_cast((Common::AlignUpPow2(width, COMPRESSED_TEXTURE_BLOCK_SIZE) / COMPRESSED_TEXTURE_BLOCK_SIZE) * \ (Common::AlignUpPow2(height, COMPRESSED_TEXTURE_BLOCK_SIZE) / COMPRESSED_TEXTURE_BLOCK_SIZE)) * \ ps) u32 width = m_width, height = m_height; mem = COMPRESSED_SIZE(width, height); for (u32 i = 1; i < m_levels; i++) { width = (width > 1) ? (width / 2) : width; height = (height > 1) ? (height / 2) : height; mem += COMPRESSED_SIZE(width, height); } #undef COMPRESSED_SIZE } else { u32 width = m_width, height = m_height; mem = static_cast(width * height) * ps; for (u32 i = 1; i < m_levels; i++) { width = (width > 1) ? (width / 2) : width; height = (height > 1) ? (height / 2) : height; mem += static_cast(width * height) * ps; } } return mem; } u32 GPUTexture::GetPixelSize(GPUTexture::Format format) { static constexpr std::array(Format::MaxCount)> sizes = {{ 0, // Unknown 4, // RGBA8 4, // BGRA8 2, // RGB565 2, // RGB5A1 2, // A1BGR5 1, // R8 2, // D16 4, // D24S8 4, // D32F 8, // D32FS8 2, // R16 2, // R16I 2, // R16U 2, // R16F 4, // R32I 4, // R32U 4, // R32F 2, // RG8 2, // RG16 2, // RG16F 8, // RG32F 8, // RGBA16 8, // RGBA16F 16, // RGBA32F 4, // RGB10A2 8, // BC1 - 16 pixels in 64 bits 16, // BC2 - 16 pixels in 128 bits 16, // BC3 - 16 pixels in 128 bits 16, // BC4 - 16 pixels in 128 bits }}; return sizes[static_cast(format)]; } bool GPUTexture::IsDepthFormat(Format format) { return (format >= Format::D16 && format <= Format::D32FS8); } bool GPUTexture::IsDepthStencilFormat(Format format) { return (format == Format::D24S8 || format == Format::D32FS8); } bool GPUTexture::ValidateConfig(u32 width, u32 height, u32 layers, u32 levels, u32 samples, Type type, Format format, Flags flags, Error* error) { if (width == 0 || width > MAX_WIDTH || height == 0 || height > MAX_HEIGHT || layers == 0 || layers > MAX_LAYERS || levels == 0 || levels > MAX_LEVELS || samples == 0 || samples > MAX_SAMPLES) { Error::SetStringFmt(error, "Invalid dimensions: {}x{}x{} {} {}.", width, height, layers, levels, samples); return false; } const u32 max_texture_size = g_gpu_device->GetMaxTextureSize(); if (width > max_texture_size || height > max_texture_size) { Error::SetStringFmt(error, "Texture width ({}) or height ({}) exceeds max texture size ({}).", width, height, max_texture_size); return false; } const u32 max_samples = g_gpu_device->GetMaxMultisamples(); if (samples > max_samples) { Error::SetStringFmt(error, "Texture samples ({}) exceeds max samples ({}).", samples, max_samples); return false; } if (samples > 1) { if (levels > 1) { Error::SetStringView(error, "Multisampled textures can't have mip levels."); return false; } else if (type != Type::RenderTarget && type != Type::DepthStencil) { Error::SetStringView(error, "Multisampled textures must be render targets or depth stencil targets."); return false; } } if (layers > 1 && type != Type::Texture) { Error::SetStringView(error, "Texture arrays are not supported on targets."); return false; } if (levels > 1 && type != Type::Texture) { Error::SetStringView(error, "Mipmaps are not supported on targets."); return false; } if ((flags & Flags::AllowGenerateMipmaps) != Flags::None && levels <= 1) { Error::SetStringView(error, "Allow generate mipmaps requires >1 level."); return false; } if ((flags & Flags::AllowBindAsImage) != Flags::None && ((type != Type::Texture && type != Type::RenderTarget) || levels > 1)) { Error::SetStringView(error, "Bind as image is not allowed on depth or mipmapped targets."); return false; } if ((flags & Flags::AllowMap) != Flags::None && (type != Type::Texture || (flags & Flags::AllowGenerateMipmaps) != Flags::None)) { Error::SetStringView(error, "Allow map is not supported on targets."); return false; } if (IsCompressedFormat(format) && (type != Type::Texture || ((flags & Flags::AllowBindAsImage) != Flags::None))) { Error::SetStringView(error, "Compressed formats are only supported for textures."); return false; } return true; } void GPUTexture::MakeReadyForSampling() { } GPUDownloadTexture::GPUDownloadTexture(u32 width, u32 height, GPUTexture::Format format, bool is_imported) : m_width(width), m_height(height), m_format(format), m_is_imported(is_imported) { } GPUDownloadTexture::~GPUDownloadTexture() = default; u32 GPUDownloadTexture::GetBufferSize(u32 width, u32 height, GPUTexture::Format format, u32 pitch_align /* = 1 */) { DebugAssert(std::has_single_bit(pitch_align)); const u32 bytes_per_pixel = GPUTexture::GetPixelSize(format); const u32 pitch = Common::AlignUpPow2(width * bytes_per_pixel, pitch_align); return (pitch * height); } u32 GPUDownloadTexture::GetTransferPitch(u32 width, u32 pitch_align) const { DebugAssert(std::has_single_bit(pitch_align)); const u32 bytes_per_pixel = GPUTexture::GetPixelSize(m_format); return Common::AlignUpPow2(width * bytes_per_pixel, pitch_align); } void GPUDownloadTexture::GetTransferSize(u32 x, u32 y, u32 width, u32 height, u32 pitch, u32* copy_offset, u32* copy_size, u32* copy_rows) const { const u32 bytes_per_pixel = GPUTexture::GetPixelSize(m_format); *copy_offset = (y * pitch) + (x * bytes_per_pixel); *copy_size = width * bytes_per_pixel; *copy_rows = height; } bool GPUDownloadTexture::ReadTexels(u32 x, u32 y, u32 width, u32 height, void* out_ptr, u32 out_stride) { if (m_needs_flush) Flush(); // if we're imported, and this is the same buffer, bail out if (m_map_pointer == out_ptr) { // but stride should match DebugAssert(x == 0 && y == 0 && width <= m_width && height <= m_height && out_stride == m_current_pitch); return true; } if (!Map(x, y, width, height)) return false; u32 copy_offset, copy_size, copy_rows; GetTransferSize(x, y, width, height, m_current_pitch, ©_offset, ©_size, ©_rows); StringUtil::StrideMemCpy(out_ptr, out_stride, m_map_pointer + copy_offset, m_current_pitch, copy_size, copy_rows); return true; }