Files
LibretroAdmin 58e6a6005d Video: pointer and size instead of std::span
Texture bytes travel as a TexBytes, a pointer and a size, where they
were a std::span<const u8>: TMEM and RAM runs into TextureInfo and its
mip levels, and the texel decoders the software renderer samples
through. Reads past the end still give zeros, now through three small
readers in TextureDecoder_Common, and TexBytesSkip() steps past a level
as SafeSubspan() did.

LoadPNGTexture() takes a pointer and a size. Metal's bounding box,
which the build does not compile, gets the Read() and Write() the other
backends have. GraphicsModGroup sorted its mods with std::ranges::sort,
whose <algorithm> came in through SpanUtils.h; an insertion sort by
weight takes its place, keeping mods of equal weight in load order.
2026-11-10 21:44:07 +01:00

237 lines
7.6 KiB
C++

// Copyright 2021 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "VideoCommon/TextureInfo.h"
#include <fmt/format.h>
#include <encodings/xxh64.h>
#include "Common/Align.h"
#include "Common/Assert.h"
#include "Common/Logging/Log.h"
#include "Core/HW/Memmap.h"
#include "Core/System.h"
#include "VideoCommon/BPMemory.h"
#include "VideoCommon/TextureDecoder.h"
TextureInfo TextureInfo::FromStage(u32 stage)
{
const auto tex = bpmem.tex.GetUnit(stage);
const auto texture_format = tex.texImage0.format;
const auto tlut_format = tex.texTlut.tlut_format;
const auto width = tex.texImage0.width + 1;
const auto height = tex.texImage0.height + 1;
const u32 address = (tex.texImage3.image_base /* & 0x1FFFFF*/) << 5;
const u32 tlutaddr = tex.texTlut.tmem_offset << 9;
const TexBytes tlut_data = TexDecoder_GetTmem(tlutaddr);
std::optional<u32> mip_count;
const bool has_mipmaps = tex.texMode0.mipmap_filter != MipMode::None;
if (has_mipmaps)
{
mip_count = (tex.texMode1.max_lod + 0xf) / 0x10;
}
const bool from_tmem = tex.texImage1.cache_manually_managed != 0;
const u32 tmem_address_even = from_tmem ? tex.texImage1.tmem_even * TMEM_LINE_SIZE : 0;
const u32 tmem_address_odd = from_tmem ? tex.texImage2.tmem_odd * TMEM_LINE_SIZE : 0;
if (from_tmem)
{
return TextureInfo(stage, TexDecoder_GetTmem(tmem_address_even), tlut_data, address,
texture_format, tlut_format, width, height, true,
TexDecoder_GetTmem(tmem_address_odd),
TexDecoder_GetTmem(tmem_address_even), mip_count);
}
auto& system = Core::System::GetInstance();
auto& memory = system.GetMemory();
TexBytes data;
data.data = memory.GetPointerAndSize(address, &data.size);
const TexBytes none = {};
return TextureInfo(stage, data, tlut_data, address, texture_format, tlut_format, width, height,
false, none, none, mip_count);
}
TextureInfo::TextureInfo(u32 stage, TexBytes data, TexBytes tlut_data, u32 address,
TextureFormat texture_format, TLUTFormat tlut_format, u32 width,
u32 height, bool from_tmem, TexBytes tmem_odd, TexBytes tmem_even,
std::optional<u32> mip_count)
: m_data(data), m_tlut_data(tlut_data), m_address(address), m_from_tmem(from_tmem),
m_tmem_even(tmem_even), m_tmem_odd(tmem_odd), m_texture_format(texture_format),
m_tlut_format(tlut_format), m_raw_width(width), m_raw_height(height), m_stage(stage)
{
const bool is_palette_texture = IsColorIndexed(m_texture_format);
if (is_palette_texture)
m_palette_size = TexDecoder_GetPaletteSize(m_texture_format);
// TexelSizeInNibbles(format) * width * height / 16;
m_block_width = TexDecoder_GetBlockWidthInTexels(m_texture_format);
m_block_height = TexDecoder_GetBlockHeightInTexels(m_texture_format);
m_expanded_width = Common::AlignUp(m_raw_width, m_block_width);
m_expanded_height = Common::AlignUp(m_raw_height, m_block_height);
m_texture_size =
TexDecoder_GetTextureSizeInBytes(m_expanded_width, m_expanded_height, m_texture_format);
if (data.size < m_texture_size)
{
ERROR_LOG_FMT(VIDEO, "Trying to use an invalid texture address {:#010x}", GetRawAddress());
m_data_valid = false;
}
else if (m_palette_size && tlut_data.size < *m_palette_size)
{
ERROR_LOG_FMT(VIDEO, "Trying to use an invalid TLUT address {:#010x}", GetRawAddress());
m_data_valid = false;
}
else
{
m_data_valid = true;
}
if (mip_count)
{
m_mipmaps_enabled = true;
const u32 raw_mip_count = *mip_count;
// GPUs don't like when the specified mipmap count would require more than one 1x1-sized LOD in
// the mipmap chain
// e.g. 64x64 with 7 LODs would have the mipmap chain 64x64,32x32,16x16,8x8,4x4,2x2,1x1,0x0, so
// we limit the mipmap count to 6 there
m_limited_mip_count =
std::min<u32>(MathUtil::IntLog2(std::max(width, height)) + 1, raw_mip_count + 1) - 1;
}
}
std::string TextureInfo::NameDetails::GetFullName() const
{
return fmt::format("{}_{}{}_{}", base_name, texture_name, tlut_name, format_name);
}
TextureInfo::NameDetails TextureInfo::CalculateTextureName() const
{
if (!IsDataValid())
return NameDetails{};
const u8* tlut = m_tlut_data.data;
size_t tlut_size = m_palette_size ? *m_palette_size : 0;
// checking for min/max on paletted textures
u32 min = 0xffff;
u32 max = 0;
switch (tlut_size)
{
case 0:
break;
case 16 * 2:
for (size_t i = 0; i < m_texture_size; i++)
{
const u32 low_nibble = m_data.data[i] & 0xf;
const u32 high_nibble = m_data.data[i] >> 4;
min = std::min({min, low_nibble, high_nibble});
max = std::max({max, low_nibble, high_nibble});
}
break;
case 256 * 2:
for (size_t i = 0; i < m_texture_size; i++)
{
const u32 texture_byte = m_data.data[i];
min = std::min(min, texture_byte);
max = std::max(max, texture_byte);
}
break;
case 16384 * 2:
for (size_t i = 0; i < m_texture_size; i += sizeof(u16))
{
const u32 texture_halfword = Common::swap16(m_data.data[i]) & 0x3fff;
min = std::min(min, texture_halfword);
max = std::max(max, texture_halfword);
}
break;
}
if (tlut_size > 0)
{
tlut_size = 2 * (max + 1 - min);
tlut += 2 * min;
}
DEBUG_ASSERT(tlut_size <= m_palette_size.value_or(0));
const u64 tex_hash = xxh64(m_data.data, m_texture_size, 0);
const u64 tlut_hash = tlut_size ? xxh64(tlut, tlut_size, 0) : 0;
return {.base_name = fmt::format("{}{}x{}{}", format_prefix, m_raw_width, m_raw_height,
m_mipmaps_enabled ? "_m" : ""),
.texture_name = fmt::format("{:016x}", tex_hash),
.tlut_name = tlut_size ? fmt::format("_{:016x}", tlut_hash) : "",
.format_name = fmt::to_string(static_cast<int>(m_texture_format))};
}
TextureInfo::MipLevels TextureInfo::GetMipMapLevels() const
{
MipLevelIterator begin;
begin.m_parent = this;
begin.m_from_tmem = m_from_tmem;
begin.m_data = TexBytesSkip(m_data, GetTextureSize());
begin.m_tmem_even = TexBytesSkip(m_tmem_even, GetTextureSize());
begin.m_tmem_odd = m_tmem_odd;
begin.CreateMipLevel();
MipLevelIterator end;
end.m_level_index = m_limited_mip_count;
return MipLevels(begin, end);
}
u32 TextureInfo::GetFullLevelSize() const
{
u32 all_mips_size = 0;
for (const auto& mip_map : GetMipMapLevels())
{
if (mip_map.IsDataValid())
all_mips_size += mip_map.GetTextureSize();
}
return m_texture_size + all_mips_size;
}
TextureInfo::MipLevel::MipLevel(u32 level, const TextureInfo& parent, TexBytes* data)
: m_level(level)
{
m_raw_width = std::max(parent.GetRawWidth() >> level, 1u);
m_raw_height = std::max(parent.GetRawHeight() >> level, 1u);
m_expanded_width = Common::AlignUp(m_raw_width, parent.GetBlockWidth());
m_expanded_height = Common::AlignUp(m_raw_height, parent.GetBlockHeight());
m_texture_size = TexDecoder_GetTextureSizeInBytes(m_expanded_width, m_expanded_height,
parent.GetTextureFormat());
m_ptr = data->data;
m_data_valid = data->size >= m_texture_size;
*data = TexBytesSkip(*data, m_texture_size);
}
TextureInfo::MipLevelIterator& TextureInfo::MipLevelIterator::operator++()
{
++m_level_index;
CreateMipLevel();
return *this;
}
void TextureInfo::MipLevelIterator::CreateMipLevel()
{
const u32 level = m_level_index + 1;
TexBytes* data = m_from_tmem ? ((level % 2) ? &m_tmem_odd : &m_tmem_even) : &m_data;
// The MipLevel constructor mutates the data argument so the next MipLevel gets the right data
m_mip_level = MipLevel(level, *m_parent, data);
}