/** ****************************************************************************** * Xenia : Xbox 360 Emulator Research Project * ****************************************************************************** * Copyright 2014 Ben Vanik. All rights reserved. * * Released under the BSD license - see LICENSE in the root for more details. * ****************************************************************************** */ #include "xenia/gpu/texture_info.h" #include #include #include #include "xenia/base/logging.h" #include "xenia/base/math.h" #include "xenia/base/memory.h" #include "xenia/base/xxhash.h" namespace xe { namespace gpu { using namespace xe::gpu::xenos; bool TextureInfo::Prepare(const xe_gpu_texture_fetch_t& fetch, TextureInfo* out_info) { // https://msdn.microsoft.com/en-us/library/windows/desktop/cc308051(v=vs.85).aspx // a2xx_sq_surfaceformat std::memset(out_info, 0, sizeof(TextureInfo)); auto& info = *out_info; info.format = fetch.format; info.endianness = fetch.endianness; info.dimension = fetch.dimension; info.width = info.height = info.depth = 0; info.is_stacked = false; switch (info.dimension) { case xenos::DataDimension::k1D: // we treat 1D textures as 2D info.dimension = DataDimension::k2DOrStacked; info.width = fetch.size_1d.width; assert_true(!fetch.stacked); break; case xenos::DataDimension::k2DOrStacked: info.width = fetch.size_2d.width; info.height = fetch.size_2d.height; if (fetch.stacked) { info.depth = fetch.size_2d.stack_depth; info.is_stacked = true; } break; case xenos::DataDimension::k3D: info.width = fetch.size_3d.width; info.height = fetch.size_3d.height; info.depth = fetch.size_3d.depth; assert_true(!fetch.stacked); break; case xenos::DataDimension::kCube: info.width = fetch.size_2d.width; info.height = fetch.size_2d.height; assert_true(fetch.size_2d.stack_depth == 5); info.depth = fetch.size_2d.stack_depth; assert_true(!fetch.stacked); break; default: assert_unhandled_case(info.dimension); break; } info.pitch = fetch.pitch << 5; info.mip_min_level = fetch.mip_min_level; info.mip_max_level = std::max(uint32_t(fetch.mip_min_level), uint32_t(fetch.mip_max_level)); info.is_tiled = fetch.tiled; info.has_packed_mips = fetch.packed_mips; info.extent = TextureExtent::Calculate(out_info, true); info.SetupMemoryInfo(fetch.base_address << 12, fetch.mip_address << 12); // We've gotten this far and mip_address is zero, assume no extra mips. if (info.mip_max_level > 0 && !info.memory.mip_address) { info.mip_max_level = 0; } assert_true(info.mip_min_level <= info.mip_max_level); return true; } bool TextureInfo::PrepareResolve(uint32_t physical_address, xenos::TextureFormat format, xenos::Endian endian, uint32_t pitch, uint32_t width, uint32_t height, uint32_t depth, TextureInfo* out_info) { assert_true(width > 0); assert_true(height > 0); std::memset(out_info, 0, sizeof(TextureInfo)); auto& info = *out_info; info.format = format; info.endianness = endian; info.dimension = xenos::DataDimension::k2DOrStacked; info.width = width - 1; info.height = height - 1; info.depth = depth - 1; info.pitch = pitch; info.mip_min_level = 0; info.mip_max_level = 0; info.is_tiled = true; info.has_packed_mips = false; info.extent = TextureExtent::Calculate(out_info, true); info.SetupMemoryInfo(physical_address, 0); return true; } uint32_t TextureInfo::GetMaxMipLevels() const { return 1 + xe::log2_floor(std::max({width + 1, height + 1, depth + 1})); } const TextureExtent TextureInfo::GetMipExtent(uint32_t mip, bool is_guest) const { if (mip == 0) { return extent; } uint32_t mip_width, mip_height; if (is_guest) { mip_width = xe::next_pow2(width + 1) >> mip; mip_height = xe::next_pow2(height + 1) >> mip; } else { mip_width = std::max(1u, (width + 1) >> mip); mip_height = std::max(1u, (height + 1) >> mip); } return TextureExtent::Calculate(format_info(), mip_width, mip_height, depth + 1, is_tiled, is_guest); } void TextureInfo::GetMipSize(uint32_t mip, uint32_t* out_width, uint32_t* out_height) const { assert_not_null(out_width); assert_not_null(out_height); if (mip == 0) { *out_width = width + 1; *out_height = height + 1; return; } uint32_t width_pow2 = xe::next_pow2(width + 1); uint32_t height_pow2 = xe::next_pow2(height + 1); *out_width = std::max(width_pow2 >> mip, 1u); *out_height = std::max(height_pow2 >> mip, 1u); } uint32_t TextureInfo::GetMipLocation(uint32_t mip, uint32_t* offset_x, uint32_t* offset_y, bool is_guest) const { if (mip == 0) { // Short-circuit. Mip 0 is always stored in base_address. if (!has_packed_mips) { *offset_x = 0; *offset_y = 0; } else { GetPackedTileOffset(0, offset_x, offset_y); } return memory.base_address; } if (!memory.mip_address) { // Short-circuit. There is no mip data. *offset_x = 0; *offset_y = 0; return 0; } uint32_t address_base, address_offset; address_base = memory.mip_address; address_offset = 0; auto bytes_per_block = format_info()->bytes_per_block(); if (!has_packed_mips) { for (uint32_t i = 1; i < mip; i++) { address_offset += GetMipExtent(i, is_guest).all_blocks() * bytes_per_block; } *offset_x = 0; *offset_y = 0; return address_base + address_offset; } uint32_t width_pow2 = xe::next_pow2(width + 1); uint32_t height_pow2 = xe::next_pow2(height + 1); // Walk forward to find the address of the mip. uint32_t packed_mip_base = 1; for (uint32_t i = packed_mip_base; i < mip; i++, packed_mip_base++) { uint32_t mip_width = std::max(width_pow2 >> i, 1u); uint32_t mip_height = std::max(height_pow2 >> i, 1u); if (std::min(mip_width, mip_height) <= 16) { // We've reached the point where the mips are packed into a single tile. break; } address_offset += GetMipExtent(i, is_guest).all_blocks() * bytes_per_block; } // Now, check if the mip is packed at an offset. GetPackedTileOffset(width_pow2 >> mip, height_pow2 >> mip, format_info(), mip - packed_mip_base, offset_x, offset_y); return address_base + address_offset; } bool TextureInfo::GetPackedTileOffset(uint32_t width, uint32_t height, const FormatInfo* format_info, int packed_tile, uint32_t* offset_x, uint32_t* offset_y) { // Tile size is 32x32, and once textures go <=16 they are packed into a // single tile together. The math here is insane. Most sourced // from graph paper and looking at dds dumps. // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 // 0 +.4x4.+ +.....8x8.....+ +............16x16............+ // 1 +.4x4.+ +.....8x8.....+ +............16x16............+ // 2 +.4x4.+ +.....8x8.....+ +............16x16............+ // 3 +.4x4.+ +.....8x8.....+ +............16x16............+ // 4 x +.....8x8.....+ +............16x16............+ // 5 +.....8x8.....+ +............16x16............+ // 6 +.....8x8.....+ +............16x16............+ // 7 +.....8x8.....+ +............16x16............+ // 8 2x2 +............16x16............+ // 9 2x2 +............16x16............+ // 0 +............16x16............+ // ... ..... // This only works for square textures, or textures that are some non-pot // <= square. As soon as the aspect ratio goes weird, the textures start to // stretch across tiles. // // The 2x2 and 1x1 squares are packed in their specific positions because // each square is the size of at least one block (which is 4x4 pixels max) // // if (tile_aligned(w) > tile_aligned(h)) { // // wider than tall, so packed horizontally // } else if (tile_aligned(w) < tile_aligned(h)) { // // taller than wide, so packed vertically // } else { // square // } // It's important to use logical sizes here, as the input sizes will be // for the entire packed tile set, not the actual texture. // The minimum dimension is what matters most: if either width or height // is <= 16 this mode kicks in. uint32_t log2_width = xe::log2_ceil(width); uint32_t log2_height = xe::log2_ceil(height); if (std::min(log2_width, log2_height) > 4) { // Too big, not packed. *offset_x = 0; *offset_y = 0; return false; } // Find the block offset of the mip. if (packed_tile < 3) { if (log2_width > log2_height) { // Wider than tall. Laid out vertically. *offset_x = 0; *offset_y = 16 >> packed_tile; } else { // Taller than wide. Laid out horizontally. *offset_x = 16 >> packed_tile; *offset_y = 0; } } else { if (log2_width > log2_height) { // Wider than tall. Laid out vertically. *offset_x = 16 >> (packed_tile - 2); *offset_y = 0; } else { // Taller than wide. Laid out horizontally. *offset_x = 0; *offset_y = 16 >> (packed_tile - 2); } } *offset_x /= format_info->block_width; *offset_y /= format_info->block_height; return true; } bool TextureInfo::GetPackedTileOffset(int packed_tile, uint32_t* offset_x, uint32_t* offset_y) const { if (!has_packed_mips) { *offset_x = 0; *offset_y = 0; return false; } return GetPackedTileOffset(xe::next_pow2(width + 1), xe::next_pow2(height + 1), format_info(), packed_tile, offset_x, offset_y); } uint64_t TextureInfo::hash() const { return XXH3_64bits(this, sizeof(TextureInfo)); } void TextureInfo::SetupMemoryInfo(uint32_t base_address, uint32_t mip_address) { uint32_t bytes_per_block = format_info()->bytes_per_block(); memory.base_address = 0; memory.base_size = 0; memory.mip_address = 0; memory.mip_size = 0; if (mip_min_level == 0 && base_address) { // There is a base mip level. memory.base_address = base_address; memory.base_size = GetMipExtent(0, true).visible_blocks() * bytes_per_block; } if (mip_min_level == 0 && mip_max_level == 0) { // Sort circuit. Only one mip. return; } if (mip_min_level == 0 && base_address == mip_address) { // TODO(gibbed): This doesn't actually make any sense. Force only one mip. // Offending title issues: #26, #45 return; } if (mip_min_level > 0) { if ((base_address && !mip_address) || (base_address == mip_address)) { // Mip data is actually at base address? mip_address = base_address; base_address = 0; } else if (!base_address && mip_address) { // Nothing needs to be done. } else { // WTF? assert_always(); } } memory.mip_address = mip_address; if (!has_packed_mips) { for (uint32_t mip = std::max(1u, mip_min_level); mip < mip_max_level; mip++) { memory.mip_size += GetMipExtent(mip, true).all_blocks() * bytes_per_block; } memory.mip_size += GetMipExtent(mip_max_level, true).visible_blocks() * bytes_per_block; return; } uint32_t width_pow2 = xe::next_pow2(width + 1); uint32_t height_pow2 = xe::next_pow2(height + 1); // Walk forward to find the address of the mip. uint32_t packed_mip_base = std::max(1u, mip_min_level); for (uint32_t mip = packed_mip_base; mip < mip_max_level; mip++, packed_mip_base++) { uint32_t mip_width = std::max(width_pow2 >> mip, 1u); uint32_t mip_height = std::max(height_pow2 >> mip, 1u); if (std::min(mip_width, mip_height) <= 16) { // We've reached the point where the mips are packed into a single tile. break; } memory.mip_size += GetMipExtent(mip, true).all_blocks() * bytes_per_block; } } } // namespace gpu } // namespace xe