Browse Source
Merge pull request #2278 from ReinUsesLisp/vc-texture-cache
Merge pull request #2278 from ReinUsesLisp/vc-texture-cache
video_core: Implement API agnostic view based texture cachence_cpp
committed by
GitHub
3 changed files with 974 additions and 0 deletions
-
2src/video_core/CMakeLists.txt
-
386src/video_core/texture_cache.cpp
-
586src/video_core/texture_cache.h
@ -0,0 +1,386 @@ |
|||||
|
// Copyright 2019 yuzu Emulator Project
|
||||
|
// Licensed under GPLv2 or any later version
|
||||
|
// Refer to the license.txt file included.
|
||||
|
|
||||
|
#include "common/alignment.h"
|
||||
|
#include "common/assert.h"
|
||||
|
#include "common/cityhash.h"
|
||||
|
#include "common/common_types.h"
|
||||
|
#include "core/core.h"
|
||||
|
#include "video_core/surface.h"
|
||||
|
#include "video_core/texture_cache.h"
|
||||
|
#include "video_core/textures/decoders.h"
|
||||
|
#include "video_core/textures/texture.h"
|
||||
|
|
||||
|
namespace VideoCommon { |
||||
|
|
||||
|
using VideoCore::Surface::SurfaceTarget; |
||||
|
|
||||
|
using VideoCore::Surface::ComponentTypeFromDepthFormat; |
||||
|
using VideoCore::Surface::ComponentTypeFromRenderTarget; |
||||
|
using VideoCore::Surface::ComponentTypeFromTexture; |
||||
|
using VideoCore::Surface::PixelFormatFromDepthFormat; |
||||
|
using VideoCore::Surface::PixelFormatFromRenderTargetFormat; |
||||
|
using VideoCore::Surface::PixelFormatFromTextureFormat; |
||||
|
using VideoCore::Surface::SurfaceTargetFromTextureType; |
||||
|
|
||||
|
constexpr u32 GetMipmapSize(bool uncompressed, u32 mip_size, u32 tile) { |
||||
|
return uncompressed ? mip_size : std::max(1U, (mip_size + tile - 1) / tile); |
||||
|
} |
||||
|
|
||||
|
SurfaceParams SurfaceParams::CreateForTexture(Core::System& system, |
||||
|
const Tegra::Texture::FullTextureInfo& config) { |
||||
|
SurfaceParams params; |
||||
|
params.is_tiled = config.tic.IsTiled(); |
||||
|
params.block_width = params.is_tiled ? config.tic.BlockWidth() : 0, |
||||
|
params.block_height = params.is_tiled ? config.tic.BlockHeight() : 0, |
||||
|
params.block_depth = params.is_tiled ? config.tic.BlockDepth() : 0, |
||||
|
params.tile_width_spacing = params.is_tiled ? (1 << config.tic.tile_width_spacing.Value()) : 1; |
||||
|
params.pixel_format = |
||||
|
PixelFormatFromTextureFormat(config.tic.format, config.tic.r_type.Value(), false); |
||||
|
params.component_type = ComponentTypeFromTexture(config.tic.r_type.Value()); |
||||
|
params.type = GetFormatType(params.pixel_format); |
||||
|
params.target = SurfaceTargetFromTextureType(config.tic.texture_type); |
||||
|
params.width = Common::AlignUp(config.tic.Width(), GetCompressionFactor(params.pixel_format)); |
||||
|
params.height = Common::AlignUp(config.tic.Height(), GetCompressionFactor(params.pixel_format)); |
||||
|
params.depth = config.tic.Depth(); |
||||
|
if (params.target == SurfaceTarget::TextureCubemap || |
||||
|
params.target == SurfaceTarget::TextureCubeArray) { |
||||
|
params.depth *= 6; |
||||
|
} |
||||
|
params.pitch = params.is_tiled ? 0 : config.tic.Pitch(); |
||||
|
params.unaligned_height = config.tic.Height(); |
||||
|
params.num_levels = config.tic.max_mip_level + 1; |
||||
|
|
||||
|
params.CalculateCachedValues(); |
||||
|
return params; |
||||
|
} |
||||
|
|
||||
|
SurfaceParams SurfaceParams::CreateForDepthBuffer( |
||||
|
Core::System& system, u32 zeta_width, u32 zeta_height, Tegra::DepthFormat format, |
||||
|
u32 block_width, u32 block_height, u32 block_depth, |
||||
|
Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type) { |
||||
|
SurfaceParams params; |
||||
|
params.is_tiled = type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear; |
||||
|
params.block_width = 1 << std::min(block_width, 5U); |
||||
|
params.block_height = 1 << std::min(block_height, 5U); |
||||
|
params.block_depth = 1 << std::min(block_depth, 5U); |
||||
|
params.tile_width_spacing = 1; |
||||
|
params.pixel_format = PixelFormatFromDepthFormat(format); |
||||
|
params.component_type = ComponentTypeFromDepthFormat(format); |
||||
|
params.type = GetFormatType(params.pixel_format); |
||||
|
params.width = zeta_width; |
||||
|
params.height = zeta_height; |
||||
|
params.unaligned_height = zeta_height; |
||||
|
params.target = SurfaceTarget::Texture2D; |
||||
|
params.depth = 1; |
||||
|
params.num_levels = 1; |
||||
|
|
||||
|
params.CalculateCachedValues(); |
||||
|
return params; |
||||
|
} |
||||
|
|
||||
|
SurfaceParams SurfaceParams::CreateForFramebuffer(Core::System& system, std::size_t index) { |
||||
|
const auto& config{system.GPU().Maxwell3D().regs.rt[index]}; |
||||
|
SurfaceParams params; |
||||
|
params.is_tiled = |
||||
|
config.memory_layout.type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear; |
||||
|
params.block_width = 1 << config.memory_layout.block_width; |
||||
|
params.block_height = 1 << config.memory_layout.block_height; |
||||
|
params.block_depth = 1 << config.memory_layout.block_depth; |
||||
|
params.tile_width_spacing = 1; |
||||
|
params.pixel_format = PixelFormatFromRenderTargetFormat(config.format); |
||||
|
params.component_type = ComponentTypeFromRenderTarget(config.format); |
||||
|
params.type = GetFormatType(params.pixel_format); |
||||
|
if (params.is_tiled) { |
||||
|
params.width = config.width; |
||||
|
} else { |
||||
|
const u32 bpp = GetFormatBpp(params.pixel_format) / CHAR_BIT; |
||||
|
params.pitch = config.width; |
||||
|
params.width = params.pitch / bpp; |
||||
|
} |
||||
|
params.height = config.height; |
||||
|
params.depth = 1; |
||||
|
params.unaligned_height = config.height; |
||||
|
params.target = SurfaceTarget::Texture2D; |
||||
|
params.num_levels = 1; |
||||
|
|
||||
|
params.CalculateCachedValues(); |
||||
|
return params; |
||||
|
} |
||||
|
|
||||
|
SurfaceParams SurfaceParams::CreateForFermiCopySurface( |
||||
|
const Tegra::Engines::Fermi2D::Regs::Surface& config) { |
||||
|
SurfaceParams params{}; |
||||
|
params.is_tiled = !config.linear; |
||||
|
params.block_width = params.is_tiled ? std::min(config.BlockWidth(), 32U) : 0, |
||||
|
params.block_height = params.is_tiled ? std::min(config.BlockHeight(), 32U) : 0, |
||||
|
params.block_depth = params.is_tiled ? std::min(config.BlockDepth(), 32U) : 0, |
||||
|
params.tile_width_spacing = 1; |
||||
|
params.pixel_format = PixelFormatFromRenderTargetFormat(config.format); |
||||
|
params.component_type = ComponentTypeFromRenderTarget(config.format); |
||||
|
params.type = GetFormatType(params.pixel_format); |
||||
|
params.width = config.width; |
||||
|
params.height = config.height; |
||||
|
params.unaligned_height = config.height; |
||||
|
// TODO(Rodrigo): Try to guess the surface target from depth and layer parameters
|
||||
|
params.target = SurfaceTarget::Texture2D; |
||||
|
params.depth = 1; |
||||
|
params.num_levels = 1; |
||||
|
|
||||
|
params.CalculateCachedValues(); |
||||
|
return params; |
||||
|
} |
||||
|
|
||||
|
u32 SurfaceParams::GetMipWidth(u32 level) const { |
||||
|
return std::max(1U, width >> level); |
||||
|
} |
||||
|
|
||||
|
u32 SurfaceParams::GetMipHeight(u32 level) const { |
||||
|
return std::max(1U, height >> level); |
||||
|
} |
||||
|
|
||||
|
u32 SurfaceParams::GetMipDepth(u32 level) const { |
||||
|
return IsLayered() ? depth : std::max(1U, depth >> level); |
||||
|
} |
||||
|
|
||||
|
bool SurfaceParams::IsLayered() const { |
||||
|
switch (target) { |
||||
|
case SurfaceTarget::Texture1DArray: |
||||
|
case SurfaceTarget::Texture2DArray: |
||||
|
case SurfaceTarget::TextureCubeArray: |
||||
|
case SurfaceTarget::TextureCubemap: |
||||
|
return true; |
||||
|
default: |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
u32 SurfaceParams::GetMipBlockHeight(u32 level) const { |
||||
|
// Auto block resizing algorithm from:
|
||||
|
// https://cgit.freedesktop.org/mesa/mesa/tree/src/gallium/drivers/nouveau/nv50/nv50_miptree.c
|
||||
|
if (level == 0) { |
||||
|
return block_height; |
||||
|
} |
||||
|
const u32 height{GetMipHeight(level)}; |
||||
|
const u32 default_block_height{GetDefaultBlockHeight(pixel_format)}; |
||||
|
const u32 blocks_in_y{(height + default_block_height - 1) / default_block_height}; |
||||
|
u32 block_height = 16; |
||||
|
while (block_height > 1 && blocks_in_y <= block_height * 4) { |
||||
|
block_height >>= 1; |
||||
|
} |
||||
|
return block_height; |
||||
|
} |
||||
|
|
||||
|
u32 SurfaceParams::GetMipBlockDepth(u32 level) const { |
||||
|
if (level == 0) |
||||
|
return block_depth; |
||||
|
if (target != SurfaceTarget::Texture3D) |
||||
|
return 1; |
||||
|
|
||||
|
const u32 depth{GetMipDepth(level)}; |
||||
|
u32 block_depth = 32; |
||||
|
while (block_depth > 1 && depth * 2 <= block_depth) { |
||||
|
block_depth >>= 1; |
||||
|
} |
||||
|
if (block_depth == 32 && GetMipBlockHeight(level) >= 4) { |
||||
|
return 16; |
||||
|
} |
||||
|
return block_depth; |
||||
|
} |
||||
|
|
||||
|
std::size_t SurfaceParams::GetGuestMipmapLevelOffset(u32 level) const { |
||||
|
std::size_t offset = 0; |
||||
|
for (u32 i = 0; i < level; i++) { |
||||
|
offset += GetInnerMipmapMemorySize(i, false, IsLayered(), false); |
||||
|
} |
||||
|
return offset; |
||||
|
} |
||||
|
|
||||
|
std::size_t SurfaceParams::GetHostMipmapLevelOffset(u32 level) const { |
||||
|
std::size_t offset = 0; |
||||
|
for (u32 i = 0; i < level; i++) { |
||||
|
offset += GetInnerMipmapMemorySize(i, true, false, false); |
||||
|
} |
||||
|
return offset; |
||||
|
} |
||||
|
|
||||
|
std::size_t SurfaceParams::GetGuestLayerSize() const { |
||||
|
return GetInnerMemorySize(false, true, false); |
||||
|
} |
||||
|
|
||||
|
std::size_t SurfaceParams::GetHostLayerSize(u32 level) const { |
||||
|
return GetInnerMipmapMemorySize(level, true, IsLayered(), false); |
||||
|
} |
||||
|
|
||||
|
bool SurfaceParams::IsFamiliar(const SurfaceParams& view_params) const { |
||||
|
if (std::tie(is_tiled, tile_width_spacing, pixel_format, component_type, type) != |
||||
|
std::tie(view_params.is_tiled, view_params.tile_width_spacing, view_params.pixel_format, |
||||
|
view_params.component_type, view_params.type)) { |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
const SurfaceTarget view_target{view_params.target}; |
||||
|
if (view_target == target) { |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
switch (target) { |
||||
|
case SurfaceTarget::Texture1D: |
||||
|
case SurfaceTarget::Texture2D: |
||||
|
case SurfaceTarget::Texture3D: |
||||
|
return false; |
||||
|
case SurfaceTarget::Texture1DArray: |
||||
|
return view_target == SurfaceTarget::Texture1D; |
||||
|
case SurfaceTarget::Texture2DArray: |
||||
|
return view_target == SurfaceTarget::Texture2D; |
||||
|
case SurfaceTarget::TextureCubemap: |
||||
|
return view_target == SurfaceTarget::Texture2D || |
||||
|
view_target == SurfaceTarget::Texture2DArray; |
||||
|
case SurfaceTarget::TextureCubeArray: |
||||
|
return view_target == SurfaceTarget::Texture2D || |
||||
|
view_target == SurfaceTarget::Texture2DArray || |
||||
|
view_target == SurfaceTarget::TextureCubemap; |
||||
|
default: |
||||
|
UNIMPLEMENTED_MSG("Unimplemented texture family={}", static_cast<u32>(target)); |
||||
|
return false; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
bool SurfaceParams::IsPixelFormatZeta() const { |
||||
|
return pixel_format >= VideoCore::Surface::PixelFormat::MaxColorFormat && |
||||
|
pixel_format < VideoCore::Surface::PixelFormat::MaxDepthStencilFormat; |
||||
|
} |
||||
|
|
||||
|
void SurfaceParams::CalculateCachedValues() { |
||||
|
guest_size_in_bytes = GetInnerMemorySize(false, false, false); |
||||
|
|
||||
|
// ASTC is uncompressed in software, in emulated as RGBA8
|
||||
|
if (IsPixelFormatASTC(pixel_format)) { |
||||
|
host_size_in_bytes = width * height * depth * 4; |
||||
|
} else { |
||||
|
host_size_in_bytes = GetInnerMemorySize(true, false, false); |
||||
|
} |
||||
|
|
||||
|
switch (target) { |
||||
|
case SurfaceTarget::Texture1D: |
||||
|
case SurfaceTarget::Texture2D: |
||||
|
case SurfaceTarget::Texture3D: |
||||
|
num_layers = 1; |
||||
|
break; |
||||
|
case SurfaceTarget::Texture1DArray: |
||||
|
case SurfaceTarget::Texture2DArray: |
||||
|
case SurfaceTarget::TextureCubemap: |
||||
|
case SurfaceTarget::TextureCubeArray: |
||||
|
num_layers = depth; |
||||
|
break; |
||||
|
default: |
||||
|
UNREACHABLE(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
std::size_t SurfaceParams::GetInnerMipmapMemorySize(u32 level, bool as_host_size, bool layer_only, |
||||
|
bool uncompressed) const { |
||||
|
const bool tiled{as_host_size ? false : is_tiled}; |
||||
|
const u32 tile_x{GetDefaultBlockWidth(pixel_format)}; |
||||
|
const u32 tile_y{GetDefaultBlockHeight(pixel_format)}; |
||||
|
const u32 width{GetMipmapSize(uncompressed, GetMipWidth(level), tile_x)}; |
||||
|
const u32 height{GetMipmapSize(uncompressed, GetMipHeight(level), tile_y)}; |
||||
|
const u32 depth{layer_only ? 1U : GetMipDepth(level)}; |
||||
|
return Tegra::Texture::CalculateSize(tiled, GetBytesPerPixel(pixel_format), width, height, |
||||
|
depth, GetMipBlockHeight(level), GetMipBlockDepth(level)); |
||||
|
} |
||||
|
|
||||
|
std::size_t SurfaceParams::GetInnerMemorySize(bool as_host_size, bool layer_only, |
||||
|
bool uncompressed) const { |
||||
|
std::size_t size = 0; |
||||
|
for (u32 level = 0; level < num_levels; ++level) { |
||||
|
size += GetInnerMipmapMemorySize(level, as_host_size, layer_only, uncompressed); |
||||
|
} |
||||
|
if (!as_host_size && is_tiled) { |
||||
|
size = Common::AlignUp(size, Tegra::Texture::GetGOBSize() * block_height * block_depth); |
||||
|
} |
||||
|
return size; |
||||
|
} |
||||
|
|
||||
|
std::map<u64, std::pair<u32, u32>> SurfaceParams::CreateViewOffsetMap() const { |
||||
|
std::map<u64, std::pair<u32, u32>> view_offset_map; |
||||
|
switch (target) { |
||||
|
case SurfaceTarget::Texture1D: |
||||
|
case SurfaceTarget::Texture2D: |
||||
|
case SurfaceTarget::Texture3D: { |
||||
|
constexpr u32 layer = 0; |
||||
|
for (u32 level = 0; level < num_levels; ++level) { |
||||
|
const std::size_t offset{GetGuestMipmapLevelOffset(level)}; |
||||
|
view_offset_map.insert({offset, {layer, level}}); |
||||
|
} |
||||
|
break; |
||||
|
} |
||||
|
case SurfaceTarget::Texture1DArray: |
||||
|
case SurfaceTarget::Texture2DArray: |
||||
|
case SurfaceTarget::TextureCubemap: |
||||
|
case SurfaceTarget::TextureCubeArray: { |
||||
|
const std::size_t layer_size{GetGuestLayerSize()}; |
||||
|
for (u32 level = 0; level < num_levels; ++level) { |
||||
|
const std::size_t level_offset{GetGuestMipmapLevelOffset(level)}; |
||||
|
for (u32 layer = 0; layer < num_layers; ++layer) { |
||||
|
const auto layer_offset{static_cast<std::size_t>(layer_size * layer)}; |
||||
|
const std::size_t offset{level_offset + layer_offset}; |
||||
|
view_offset_map.insert({offset, {layer, level}}); |
||||
|
} |
||||
|
} |
||||
|
break; |
||||
|
} |
||||
|
default: |
||||
|
UNIMPLEMENTED_MSG("Unimplemented surface target {}", static_cast<u32>(target)); |
||||
|
} |
||||
|
return view_offset_map; |
||||
|
} |
||||
|
|
||||
|
bool SurfaceParams::IsViewValid(const SurfaceParams& view_params, u32 layer, u32 level) const { |
||||
|
return IsDimensionValid(view_params, level) && IsDepthValid(view_params, level) && |
||||
|
IsInBounds(view_params, layer, level); |
||||
|
} |
||||
|
|
||||
|
bool SurfaceParams::IsDimensionValid(const SurfaceParams& view_params, u32 level) const { |
||||
|
return view_params.width == GetMipWidth(level) && view_params.height == GetMipHeight(level); |
||||
|
} |
||||
|
|
||||
|
bool SurfaceParams::IsDepthValid(const SurfaceParams& view_params, u32 level) const { |
||||
|
if (view_params.target != SurfaceTarget::Texture3D) { |
||||
|
return true; |
||||
|
} |
||||
|
return view_params.depth == GetMipDepth(level); |
||||
|
} |
||||
|
|
||||
|
bool SurfaceParams::IsInBounds(const SurfaceParams& view_params, u32 layer, u32 level) const { |
||||
|
return layer + view_params.num_layers <= num_layers && |
||||
|
level + view_params.num_levels <= num_levels; |
||||
|
} |
||||
|
|
||||
|
std::size_t HasheableSurfaceParams::Hash() const { |
||||
|
return static_cast<std::size_t>( |
||||
|
Common::CityHash64(reinterpret_cast<const char*>(this), sizeof(*this))); |
||||
|
} |
||||
|
|
||||
|
bool HasheableSurfaceParams::operator==(const HasheableSurfaceParams& rhs) const { |
||||
|
return std::tie(is_tiled, block_width, block_height, block_depth, tile_width_spacing, width, |
||||
|
height, depth, pitch, unaligned_height, num_levels, pixel_format, |
||||
|
component_type, type, target) == |
||||
|
std::tie(rhs.is_tiled, rhs.block_width, rhs.block_height, rhs.block_depth, |
||||
|
rhs.tile_width_spacing, rhs.width, rhs.height, rhs.depth, rhs.pitch, |
||||
|
rhs.unaligned_height, rhs.num_levels, rhs.pixel_format, rhs.component_type, |
||||
|
rhs.type, rhs.target); |
||||
|
} |
||||
|
|
||||
|
std::size_t ViewKey::Hash() const { |
||||
|
return static_cast<std::size_t>( |
||||
|
Common::CityHash64(reinterpret_cast<const char*>(this), sizeof(*this))); |
||||
|
} |
||||
|
|
||||
|
bool ViewKey::operator==(const ViewKey& rhs) const { |
||||
|
return std::tie(base_layer, num_layers, base_level, num_levels) == |
||||
|
std::tie(rhs.base_layer, rhs.num_layers, rhs.base_level, rhs.num_levels); |
||||
|
} |
||||
|
|
||||
|
} // namespace VideoCommon
|
||||
@ -0,0 +1,586 @@ |
|||||
|
// Copyright 2019 yuzu Emulator Project |
||||
|
// Licensed under GPLv2 or any later version |
||||
|
// Refer to the license.txt file included. |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <list> |
||||
|
#include <memory> |
||||
|
#include <set> |
||||
|
#include <tuple> |
||||
|
#include <type_traits> |
||||
|
#include <unordered_map> |
||||
|
|
||||
|
#include <boost/icl/interval_map.hpp> |
||||
|
#include <boost/range/iterator_range.hpp> |
||||
|
|
||||
|
#include "common/assert.h" |
||||
|
#include "common/common_types.h" |
||||
|
#include "core/memory.h" |
||||
|
#include "video_core/engines/fermi_2d.h" |
||||
|
#include "video_core/engines/maxwell_3d.h" |
||||
|
#include "video_core/gpu.h" |
||||
|
#include "video_core/rasterizer_interface.h" |
||||
|
#include "video_core/surface.h" |
||||
|
|
||||
|
namespace Core { |
||||
|
class System; |
||||
|
} |
||||
|
|
||||
|
namespace Tegra::Texture { |
||||
|
struct FullTextureInfo; |
||||
|
} |
||||
|
|
||||
|
namespace VideoCore { |
||||
|
class RasterizerInterface; |
||||
|
} |
||||
|
|
||||
|
namespace VideoCommon { |
||||
|
|
||||
|
class HasheableSurfaceParams { |
||||
|
public: |
||||
|
std::size_t Hash() const; |
||||
|
|
||||
|
bool operator==(const HasheableSurfaceParams& rhs) const; |
||||
|
|
||||
|
protected: |
||||
|
// Avoid creation outside of a managed environment. |
||||
|
HasheableSurfaceParams() = default; |
||||
|
|
||||
|
bool is_tiled; |
||||
|
u32 block_width; |
||||
|
u32 block_height; |
||||
|
u32 block_depth; |
||||
|
u32 tile_width_spacing; |
||||
|
u32 width; |
||||
|
u32 height; |
||||
|
u32 depth; |
||||
|
u32 pitch; |
||||
|
u32 unaligned_height; |
||||
|
u32 num_levels; |
||||
|
VideoCore::Surface::PixelFormat pixel_format; |
||||
|
VideoCore::Surface::ComponentType component_type; |
||||
|
VideoCore::Surface::SurfaceType type; |
||||
|
VideoCore::Surface::SurfaceTarget target; |
||||
|
}; |
||||
|
|
||||
|
class SurfaceParams final : public HasheableSurfaceParams { |
||||
|
public: |
||||
|
/// Creates SurfaceCachedParams from a texture configuration. |
||||
|
static SurfaceParams CreateForTexture(Core::System& system, |
||||
|
const Tegra::Texture::FullTextureInfo& config); |
||||
|
|
||||
|
/// Creates SurfaceCachedParams for a depth buffer configuration. |
||||
|
static SurfaceParams CreateForDepthBuffer( |
||||
|
Core::System& system, u32 zeta_width, u32 zeta_height, Tegra::DepthFormat format, |
||||
|
u32 block_width, u32 block_height, u32 block_depth, |
||||
|
Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type); |
||||
|
|
||||
|
/// Creates SurfaceCachedParams from a framebuffer configuration. |
||||
|
static SurfaceParams CreateForFramebuffer(Core::System& system, std::size_t index); |
||||
|
|
||||
|
/// Creates SurfaceCachedParams from a Fermi2D surface configuration. |
||||
|
static SurfaceParams CreateForFermiCopySurface( |
||||
|
const Tegra::Engines::Fermi2D::Regs::Surface& config); |
||||
|
|
||||
|
bool IsTiled() const { |
||||
|
return is_tiled; |
||||
|
} |
||||
|
|
||||
|
u32 GetBlockWidth() const { |
||||
|
return block_width; |
||||
|
} |
||||
|
|
||||
|
u32 GetTileWidthSpacing() const { |
||||
|
return tile_width_spacing; |
||||
|
} |
||||
|
|
||||
|
u32 GetWidth() const { |
||||
|
return width; |
||||
|
} |
||||
|
|
||||
|
u32 GetHeight() const { |
||||
|
return height; |
||||
|
} |
||||
|
|
||||
|
u32 GetDepth() const { |
||||
|
return depth; |
||||
|
} |
||||
|
|
||||
|
u32 GetPitch() const { |
||||
|
return pitch; |
||||
|
} |
||||
|
|
||||
|
u32 GetNumLevels() const { |
||||
|
return num_levels; |
||||
|
} |
||||
|
|
||||
|
VideoCore::Surface::PixelFormat GetPixelFormat() const { |
||||
|
return pixel_format; |
||||
|
} |
||||
|
|
||||
|
VideoCore::Surface::ComponentType GetComponentType() const { |
||||
|
return component_type; |
||||
|
} |
||||
|
|
||||
|
VideoCore::Surface::SurfaceTarget GetTarget() const { |
||||
|
return target; |
||||
|
} |
||||
|
|
||||
|
VideoCore::Surface::SurfaceType GetType() const { |
||||
|
return type; |
||||
|
} |
||||
|
|
||||
|
std::size_t GetGuestSizeInBytes() const { |
||||
|
return guest_size_in_bytes; |
||||
|
} |
||||
|
|
||||
|
std::size_t GetHostSizeInBytes() const { |
||||
|
return host_size_in_bytes; |
||||
|
} |
||||
|
|
||||
|
u32 GetNumLayers() const { |
||||
|
return num_layers; |
||||
|
} |
||||
|
|
||||
|
/// Returns the width of a given mipmap level. |
||||
|
u32 GetMipWidth(u32 level) const; |
||||
|
|
||||
|
/// Returns the height of a given mipmap level. |
||||
|
u32 GetMipHeight(u32 level) const; |
||||
|
|
||||
|
/// Returns the depth of a given mipmap level. |
||||
|
u32 GetMipDepth(u32 level) const; |
||||
|
|
||||
|
/// Returns true if these parameters are from a layered surface. |
||||
|
bool IsLayered() const; |
||||
|
|
||||
|
/// Returns the block height of a given mipmap level. |
||||
|
u32 GetMipBlockHeight(u32 level) const; |
||||
|
|
||||
|
/// Returns the block depth of a given mipmap level. |
||||
|
u32 GetMipBlockDepth(u32 level) const; |
||||
|
|
||||
|
/// Returns the offset in bytes in guest memory of a given mipmap level. |
||||
|
std::size_t GetGuestMipmapLevelOffset(u32 level) const; |
||||
|
|
||||
|
/// Returns the offset in bytes in host memory (linear) of a given mipmap level. |
||||
|
std::size_t GetHostMipmapLevelOffset(u32 level) const; |
||||
|
|
||||
|
/// Returns the size of a layer in bytes in guest memory. |
||||
|
std::size_t GetGuestLayerSize() const; |
||||
|
|
||||
|
/// Returns the size of a layer in bytes in host memory for a given mipmap level. |
||||
|
std::size_t GetHostLayerSize(u32 level) const; |
||||
|
|
||||
|
/// Returns true if another surface can be familiar with this. This is a loosely defined term |
||||
|
/// that reflects the possibility of these two surface parameters potentially being part of a |
||||
|
/// bigger superset. |
||||
|
bool IsFamiliar(const SurfaceParams& view_params) const; |
||||
|
|
||||
|
/// Returns true if the pixel format is a depth and/or stencil format. |
||||
|
bool IsPixelFormatZeta() const; |
||||
|
|
||||
|
/// Creates a map that redirects an address difference to a layer and mipmap level. |
||||
|
std::map<u64, std::pair<u32, u32>> CreateViewOffsetMap() const; |
||||
|
|
||||
|
/// Returns true if the passed surface view parameters is equal or a valid subset of this. |
||||
|
bool IsViewValid(const SurfaceParams& view_params, u32 layer, u32 level) const; |
||||
|
|
||||
|
private: |
||||
|
/// Calculates values that can be deduced from HasheableSurfaceParams. |
||||
|
void CalculateCachedValues(); |
||||
|
|
||||
|
/// Returns the size of a given mipmap level. |
||||
|
std::size_t GetInnerMipmapMemorySize(u32 level, bool as_host_size, bool layer_only, |
||||
|
bool uncompressed) const; |
||||
|
|
||||
|
/// Returns the size of all mipmap levels and aligns as needed. |
||||
|
std::size_t GetInnerMemorySize(bool as_host_size, bool layer_only, bool uncompressed) const; |
||||
|
|
||||
|
/// Returns true if the passed view width and height match the size of this params in a given |
||||
|
/// mipmap level. |
||||
|
bool IsDimensionValid(const SurfaceParams& view_params, u32 level) const; |
||||
|
|
||||
|
/// Returns true if the passed view depth match the size of this params in a given mipmap level. |
||||
|
bool IsDepthValid(const SurfaceParams& view_params, u32 level) const; |
||||
|
|
||||
|
/// Returns true if the passed view layers and mipmap levels are in bounds. |
||||
|
bool IsInBounds(const SurfaceParams& view_params, u32 layer, u32 level) const; |
||||
|
|
||||
|
std::size_t guest_size_in_bytes; |
||||
|
std::size_t host_size_in_bytes; |
||||
|
u32 num_layers; |
||||
|
}; |
||||
|
|
||||
|
struct ViewKey { |
||||
|
std::size_t Hash() const; |
||||
|
|
||||
|
bool operator==(const ViewKey& rhs) const; |
||||
|
|
||||
|
u32 base_layer{}; |
||||
|
u32 num_layers{}; |
||||
|
u32 base_level{}; |
||||
|
u32 num_levels{}; |
||||
|
}; |
||||
|
|
||||
|
} // namespace VideoCommon |
||||
|
|
||||
|
namespace std { |
||||
|
|
||||
|
template <> |
||||
|
struct hash<VideoCommon::SurfaceParams> { |
||||
|
std::size_t operator()(const VideoCommon::SurfaceParams& k) const noexcept { |
||||
|
return k.Hash(); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
template <> |
||||
|
struct hash<VideoCommon::ViewKey> { |
||||
|
std::size_t operator()(const VideoCommon::ViewKey& k) const noexcept { |
||||
|
return k.Hash(); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
} // namespace std |
||||
|
|
||||
|
namespace VideoCommon { |
||||
|
|
||||
|
template <typename TView, typename TExecutionContext> |
||||
|
class SurfaceBase { |
||||
|
static_assert(std::is_trivially_copyable_v<TExecutionContext>); |
||||
|
|
||||
|
public: |
||||
|
virtual void LoadBuffer() = 0; |
||||
|
|
||||
|
virtual TExecutionContext FlushBuffer(TExecutionContext exctx) = 0; |
||||
|
|
||||
|
virtual TExecutionContext UploadTexture(TExecutionContext exctx) = 0; |
||||
|
|
||||
|
TView* TryGetView(VAddr view_addr, const SurfaceParams& view_params) { |
||||
|
if (view_addr < cpu_addr || !params.IsFamiliar(view_params)) { |
||||
|
// It can't be a view if it's in a prior address. |
||||
|
return {}; |
||||
|
} |
||||
|
|
||||
|
const auto relative_offset{static_cast<u64>(view_addr - cpu_addr)}; |
||||
|
const auto it{view_offset_map.find(relative_offset)}; |
||||
|
if (it == view_offset_map.end()) { |
||||
|
// Couldn't find an aligned view. |
||||
|
return {}; |
||||
|
} |
||||
|
const auto [layer, level] = it->second; |
||||
|
|
||||
|
if (!params.IsViewValid(view_params, layer, level)) { |
||||
|
return {}; |
||||
|
} |
||||
|
|
||||
|
return GetView(layer, view_params.GetNumLayers(), level, view_params.GetNumLevels()); |
||||
|
} |
||||
|
|
||||
|
VAddr GetCpuAddr() const { |
||||
|
ASSERT(is_registered); |
||||
|
return cpu_addr; |
||||
|
} |
||||
|
|
||||
|
u8* GetHostPtr() const { |
||||
|
ASSERT(is_registered); |
||||
|
return host_ptr; |
||||
|
} |
||||
|
|
||||
|
CacheAddr GetCacheAddr() const { |
||||
|
ASSERT(is_registered); |
||||
|
return cache_addr; |
||||
|
} |
||||
|
|
||||
|
std::size_t GetSizeInBytes() const { |
||||
|
return params.GetGuestSizeInBytes(); |
||||
|
} |
||||
|
|
||||
|
void MarkAsModified(bool is_modified_) { |
||||
|
is_modified = is_modified_; |
||||
|
} |
||||
|
|
||||
|
const SurfaceParams& GetSurfaceParams() const { |
||||
|
return params; |
||||
|
} |
||||
|
|
||||
|
TView* GetView(VAddr view_addr, const SurfaceParams& view_params) { |
||||
|
TView* view{TryGetView(view_addr, view_params)}; |
||||
|
ASSERT(view != nullptr); |
||||
|
return view; |
||||
|
} |
||||
|
|
||||
|
void Register(VAddr cpu_addr_, u8* host_ptr_) { |
||||
|
ASSERT(!is_registered); |
||||
|
is_registered = true; |
||||
|
cpu_addr = cpu_addr_; |
||||
|
host_ptr = host_ptr_; |
||||
|
cache_addr = ToCacheAddr(host_ptr_); |
||||
|
} |
||||
|
|
||||
|
void Register(VAddr cpu_addr_) { |
||||
|
Register(cpu_addr_, Memory::GetPointer(cpu_addr_)); |
||||
|
} |
||||
|
|
||||
|
void Unregister() { |
||||
|
ASSERT(is_registered); |
||||
|
is_registered = false; |
||||
|
} |
||||
|
|
||||
|
bool IsRegistered() const { |
||||
|
return is_registered; |
||||
|
} |
||||
|
|
||||
|
protected: |
||||
|
explicit SurfaceBase(const SurfaceParams& params) |
||||
|
: params{params}, view_offset_map{params.CreateViewOffsetMap()} {} |
||||
|
|
||||
|
~SurfaceBase() = default; |
||||
|
|
||||
|
virtual std::unique_ptr<TView> CreateView(const ViewKey& view_key) = 0; |
||||
|
|
||||
|
bool IsModified() const { |
||||
|
return is_modified; |
||||
|
} |
||||
|
|
||||
|
const SurfaceParams params; |
||||
|
|
||||
|
private: |
||||
|
TView* GetView(u32 base_layer, u32 num_layers, u32 base_level, u32 num_levels) { |
||||
|
const ViewKey key{base_layer, num_layers, base_level, num_levels}; |
||||
|
const auto [entry, is_cache_miss] = views.try_emplace(key); |
||||
|
auto& view{entry->second}; |
||||
|
if (is_cache_miss) { |
||||
|
view = CreateView(key); |
||||
|
} |
||||
|
return view.get(); |
||||
|
} |
||||
|
|
||||
|
const std::map<u64, std::pair<u32, u32>> view_offset_map; |
||||
|
|
||||
|
VAddr cpu_addr{}; |
||||
|
u8* host_ptr{}; |
||||
|
CacheAddr cache_addr{}; |
||||
|
bool is_modified{}; |
||||
|
bool is_registered{}; |
||||
|
std::unordered_map<ViewKey, std::unique_ptr<TView>> views; |
||||
|
}; |
||||
|
|
||||
|
template <typename TSurface, typename TView, typename TExecutionContext> |
||||
|
class TextureCache { |
||||
|
static_assert(std::is_trivially_copyable_v<TExecutionContext>); |
||||
|
using ResultType = std::tuple<TView*, TExecutionContext>; |
||||
|
using IntervalMap = boost::icl::interval_map<CacheAddr, std::set<TSurface*>>; |
||||
|
using IntervalType = typename IntervalMap::interval_type; |
||||
|
|
||||
|
public: |
||||
|
void InvalidateRegion(CacheAddr addr, std::size_t size) { |
||||
|
for (TSurface* surface : GetSurfacesInRegion(addr, size)) { |
||||
|
if (!surface->IsRegistered()) { |
||||
|
// Skip duplicates |
||||
|
continue; |
||||
|
} |
||||
|
Unregister(surface); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
ResultType GetTextureSurface(TExecutionContext exctx, |
||||
|
const Tegra::Texture::FullTextureInfo& config) { |
||||
|
auto& memory_manager{system.GPU().MemoryManager()}; |
||||
|
const auto cpu_addr{memory_manager.GpuToCpuAddress(config.tic.Address())}; |
||||
|
if (!cpu_addr) { |
||||
|
return {{}, exctx}; |
||||
|
} |
||||
|
const auto params{SurfaceParams::CreateForTexture(system, config)}; |
||||
|
return GetSurfaceView(exctx, *cpu_addr, params, true); |
||||
|
} |
||||
|
|
||||
|
ResultType GetDepthBufferSurface(TExecutionContext exctx, bool preserve_contents) { |
||||
|
const auto& regs{system.GPU().Maxwell3D().regs}; |
||||
|
if (!regs.zeta.Address() || !regs.zeta_enable) { |
||||
|
return {{}, exctx}; |
||||
|
} |
||||
|
|
||||
|
auto& memory_manager{system.GPU().MemoryManager()}; |
||||
|
const auto cpu_addr{memory_manager.GpuToCpuAddress(regs.zeta.Address())}; |
||||
|
if (!cpu_addr) { |
||||
|
return {{}, exctx}; |
||||
|
} |
||||
|
|
||||
|
const auto depth_params{SurfaceParams::CreateForDepthBuffer( |
||||
|
system, regs.zeta_width, regs.zeta_height, regs.zeta.format, |
||||
|
regs.zeta.memory_layout.block_width, regs.zeta.memory_layout.block_height, |
||||
|
regs.zeta.memory_layout.block_depth, regs.zeta.memory_layout.type)}; |
||||
|
return GetSurfaceView(exctx, *cpu_addr, depth_params, preserve_contents); |
||||
|
} |
||||
|
|
||||
|
ResultType GetColorBufferSurface(TExecutionContext exctx, std::size_t index, |
||||
|
bool preserve_contents) { |
||||
|
ASSERT(index < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets); |
||||
|
|
||||
|
const auto& regs{system.GPU().Maxwell3D().regs}; |
||||
|
if (index >= regs.rt_control.count || regs.rt[index].Address() == 0 || |
||||
|
regs.rt[index].format == Tegra::RenderTargetFormat::NONE) { |
||||
|
return {{}, exctx}; |
||||
|
} |
||||
|
|
||||
|
auto& memory_manager{system.GPU().MemoryManager()}; |
||||
|
const auto& config{system.GPU().Maxwell3D().regs.rt[index]}; |
||||
|
const auto cpu_addr{memory_manager.GpuToCpuAddress( |
||||
|
config.Address() + config.base_layer * config.layer_stride * sizeof(u32))}; |
||||
|
if (!cpu_addr) { |
||||
|
return {{}, exctx}; |
||||
|
} |
||||
|
|
||||
|
return GetSurfaceView(exctx, *cpu_addr, SurfaceParams::CreateForFramebuffer(system, index), |
||||
|
preserve_contents); |
||||
|
} |
||||
|
|
||||
|
ResultType GetFermiSurface(TExecutionContext exctx, |
||||
|
const Tegra::Engines::Fermi2D::Regs::Surface& config) { |
||||
|
const auto cpu_addr{system.GPU().MemoryManager().GpuToCpuAddress(config.Address())}; |
||||
|
ASSERT(cpu_addr); |
||||
|
return GetSurfaceView(exctx, *cpu_addr, SurfaceParams::CreateForFermiCopySurface(config), |
||||
|
true); |
||||
|
} |
||||
|
|
||||
|
TSurface* TryFindFramebufferSurface(const u8* host_ptr) const { |
||||
|
const auto it{registered_surfaces.find(ToCacheAddr(host_ptr))}; |
||||
|
return it != registered_surfaces.end() ? *it->second.begin() : nullptr; |
||||
|
} |
||||
|
|
||||
|
protected: |
||||
|
TextureCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer) |
||||
|
: system{system}, rasterizer{rasterizer} {} |
||||
|
|
||||
|
~TextureCache() = default; |
||||
|
|
||||
|
virtual ResultType TryFastGetSurfaceView(TExecutionContext exctx, VAddr cpu_addr, u8* host_ptr, |
||||
|
const SurfaceParams& params, bool preserve_contents, |
||||
|
const std::vector<TSurface*>& overlaps) = 0; |
||||
|
|
||||
|
virtual std::unique_ptr<TSurface> CreateSurface(const SurfaceParams& params) = 0; |
||||
|
|
||||
|
void Register(TSurface* surface, VAddr cpu_addr, u8* host_ptr) { |
||||
|
surface->Register(cpu_addr, host_ptr); |
||||
|
registered_surfaces.add({GetSurfaceInterval(surface), {surface}}); |
||||
|
rasterizer.UpdatePagesCachedCount(surface->GetCpuAddr(), surface->GetSizeInBytes(), 1); |
||||
|
} |
||||
|
|
||||
|
void Unregister(TSurface* surface) { |
||||
|
registered_surfaces.subtract({GetSurfaceInterval(surface), {surface}}); |
||||
|
rasterizer.UpdatePagesCachedCount(surface->GetCpuAddr(), surface->GetSizeInBytes(), -1); |
||||
|
surface->Unregister(); |
||||
|
} |
||||
|
|
||||
|
TSurface* GetUncachedSurface(const SurfaceParams& params) { |
||||
|
if (TSurface* surface = TryGetReservedSurface(params); surface) |
||||
|
return surface; |
||||
|
// No reserved surface available, create a new one and reserve it |
||||
|
auto new_surface{CreateSurface(params)}; |
||||
|
TSurface* surface{new_surface.get()}; |
||||
|
ReserveSurface(params, std::move(new_surface)); |
||||
|
return surface; |
||||
|
} |
||||
|
|
||||
|
Core::System& system; |
||||
|
|
||||
|
private: |
||||
|
ResultType GetSurfaceView(TExecutionContext exctx, VAddr cpu_addr, const SurfaceParams& params, |
||||
|
bool preserve_contents) { |
||||
|
const auto host_ptr{Memory::GetPointer(cpu_addr)}; |
||||
|
const auto cache_addr{ToCacheAddr(host_ptr)}; |
||||
|
const auto overlaps{GetSurfacesInRegion(cache_addr, params.GetGuestSizeInBytes())}; |
||||
|
if (overlaps.empty()) { |
||||
|
return LoadSurfaceView(exctx, cpu_addr, host_ptr, params, preserve_contents); |
||||
|
} |
||||
|
|
||||
|
if (overlaps.size() == 1) { |
||||
|
if (TView* view = overlaps[0]->TryGetView(cpu_addr, params); view) |
||||
|
return {view, exctx}; |
||||
|
} |
||||
|
|
||||
|
TView* fast_view; |
||||
|
std::tie(fast_view, exctx) = |
||||
|
TryFastGetSurfaceView(exctx, cpu_addr, host_ptr, params, preserve_contents, overlaps); |
||||
|
|
||||
|
for (TSurface* surface : overlaps) { |
||||
|
if (!fast_view) { |
||||
|
// Flush even when we don't care about the contents, to preserve memory not written |
||||
|
// by the new surface. |
||||
|
exctx = surface->FlushBuffer(exctx); |
||||
|
} |
||||
|
Unregister(surface); |
||||
|
} |
||||
|
|
||||
|
if (fast_view) { |
||||
|
return {fast_view, exctx}; |
||||
|
} |
||||
|
|
||||
|
return LoadSurfaceView(exctx, cpu_addr, host_ptr, params, preserve_contents); |
||||
|
} |
||||
|
|
||||
|
ResultType LoadSurfaceView(TExecutionContext exctx, VAddr cpu_addr, u8* host_ptr, |
||||
|
const SurfaceParams& params, bool preserve_contents) { |
||||
|
TSurface* new_surface{GetUncachedSurface(params)}; |
||||
|
Register(new_surface, cpu_addr, host_ptr); |
||||
|
if (preserve_contents) { |
||||
|
exctx = LoadSurface(exctx, new_surface); |
||||
|
} |
||||
|
return {new_surface->GetView(cpu_addr, params), exctx}; |
||||
|
} |
||||
|
|
||||
|
TExecutionContext LoadSurface(TExecutionContext exctx, TSurface* surface) { |
||||
|
surface->LoadBuffer(); |
||||
|
exctx = surface->UploadTexture(exctx); |
||||
|
surface->MarkAsModified(false); |
||||
|
return exctx; |
||||
|
} |
||||
|
|
||||
|
std::vector<TSurface*> GetSurfacesInRegion(CacheAddr cache_addr, std::size_t size) const { |
||||
|
if (size == 0) { |
||||
|
return {}; |
||||
|
} |
||||
|
const IntervalType interval{cache_addr, cache_addr + size}; |
||||
|
|
||||
|
std::vector<TSurface*> surfaces; |
||||
|
for (auto& pair : boost::make_iterator_range(registered_surfaces.equal_range(interval))) { |
||||
|
surfaces.push_back(*pair.second.begin()); |
||||
|
} |
||||
|
return surfaces; |
||||
|
} |
||||
|
|
||||
|
void ReserveSurface(const SurfaceParams& params, std::unique_ptr<TSurface> surface) { |
||||
|
surface_reserve[params].push_back(std::move(surface)); |
||||
|
} |
||||
|
|
||||
|
TSurface* TryGetReservedSurface(const SurfaceParams& params) { |
||||
|
auto search{surface_reserve.find(params)}; |
||||
|
if (search == surface_reserve.end()) { |
||||
|
return {}; |
||||
|
} |
||||
|
for (auto& surface : search->second) { |
||||
|
if (!surface->IsRegistered()) { |
||||
|
return surface.get(); |
||||
|
} |
||||
|
} |
||||
|
return {}; |
||||
|
} |
||||
|
|
||||
|
IntervalType GetSurfaceInterval(TSurface* surface) const { |
||||
|
return IntervalType::right_open(surface->GetCacheAddr(), |
||||
|
surface->GetCacheAddr() + surface->GetSizeInBytes()); |
||||
|
} |
||||
|
|
||||
|
VideoCore::RasterizerInterface& rasterizer; |
||||
|
|
||||
|
IntervalMap registered_surfaces; |
||||
|
|
||||
|
/// The surface reserve is a "backup" cache, this is where we put unique surfaces that have |
||||
|
/// previously been used. This is to prevent surfaces from being constantly created and |
||||
|
/// destroyed when used with different surface parameters. |
||||
|
std::unordered_map<SurfaceParams, std::list<std::unique_ptr<TSurface>>> surface_reserve; |
||||
|
}; |
||||
|
|
||||
|
} // namespace VideoCommon |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue