citra/src/video_core/rasterizer_cache/surface_base.cpp

182 lines
6.6 KiB
C++

// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/alignment.h"
#include "video_core/custom_textures/material.h"
#include "video_core/rasterizer_cache/surface_base.h"
#include "video_core/texture/texture_decode.h"
namespace VideoCore {
SurfaceBase::SurfaceBase(const SurfaceParams& params) : SurfaceParams{params} {}
SurfaceBase::~SurfaceBase() = default;
bool SurfaceBase::CanFill(const SurfaceParams& dest_surface, SurfaceInterval fill_interval) const {
if (type == SurfaceType::Fill && IsRegionValid(fill_interval) &&
boost::icl::first(fill_interval) >= addr &&
boost::icl::last_next(fill_interval) <= end && // dest_surface is within our fill range
dest_surface.FromInterval(fill_interval).GetInterval() ==
fill_interval) { // make sure interval is a rectangle in dest surface
if (fill_size * 8 != dest_surface.GetFormatBpp()) {
// Check if bits repeat for our fill_size
const u32 dest_bytes_per_pixel = std::max(dest_surface.GetFormatBpp() / 8, 1u);
std::vector<u8> fill_test(fill_size * dest_bytes_per_pixel);
for (u32 i = 0; i < dest_bytes_per_pixel; ++i) {
std::memcpy(&fill_test[i * fill_size], &fill_data[0], fill_size);
}
for (u32 i = 0; i < fill_size; ++i) {
if (std::memcmp(&fill_test[dest_bytes_per_pixel * i], &fill_test[0],
dest_bytes_per_pixel) != 0) {
return false;
}
}
if (dest_surface.GetFormatBpp() == 4 && (fill_test[0] & 0xF) != (fill_test[0] >> 4)) {
return false;
}
}
return true;
}
return false;
}
bool SurfaceBase::CanCopy(const SurfaceParams& dest_surface, SurfaceInterval copy_interval) const {
const SurfaceParams subrect_params = dest_surface.FromInterval(copy_interval);
ASSERT(subrect_params.GetInterval() == copy_interval);
if (CanSubRect(subrect_params)) {
return true;
}
if (CanFill(dest_surface, copy_interval)) {
return true;
}
return false;
}
SurfaceInterval SurfaceBase::GetCopyableInterval(const SurfaceParams& params) const {
SurfaceInterval result{};
const u32 tile_align = params.BytesInPixels(params.is_tiled ? 8 * 8 : 1);
const auto valid_regions =
SurfaceRegions{params.GetInterval() & GetInterval()} - invalid_regions;
for (auto& valid_interval : valid_regions) {
const SurfaceInterval aligned_interval{
params.addr +
Common::AlignUp(boost::icl::first(valid_interval) - params.addr, tile_align),
params.addr +
Common::AlignDown(boost::icl::last_next(valid_interval) - params.addr, tile_align)};
if (tile_align > boost::icl::length(valid_interval) ||
boost::icl::length(aligned_interval) == 0) {
continue;
}
// Get the rectangle within aligned_interval
const u32 stride_bytes = params.BytesInPixels(params.stride) * (params.is_tiled ? 8 : 1);
SurfaceInterval rect_interval{
params.addr +
Common::AlignUp(boost::icl::first(aligned_interval) - params.addr, stride_bytes),
params.addr + Common::AlignDown(boost::icl::last_next(aligned_interval) - params.addr,
stride_bytes),
};
if (boost::icl::first(rect_interval) > boost::icl::last_next(rect_interval)) {
// 1 row
rect_interval = aligned_interval;
} else if (boost::icl::length(rect_interval) == 0) {
// 2 rows that do not make a rectangle, return the larger one
const SurfaceInterval row1{boost::icl::first(aligned_interval),
boost::icl::first(rect_interval)};
const SurfaceInterval row2{boost::icl::first(rect_interval),
boost::icl::last_next(aligned_interval)};
rect_interval = (boost::icl::length(row1) > boost::icl::length(row2)) ? row1 : row2;
}
if (boost::icl::length(rect_interval) > boost::icl::length(result)) {
result = rect_interval;
}
}
return result;
}
Extent SurfaceBase::RealExtent(bool scaled) const {
const bool is_custom = IsCustom();
u32 real_width = width;
u32 real_height = height;
if (is_custom) {
real_width = material->width;
real_height = material->height;
} else if (scaled) {
real_width = GetScaledWidth();
real_height = GetScaledHeight();
}
return Extent{
.width = real_width,
.height = real_height,
};
}
bool SurfaceBase::HasNormalMap() const noexcept {
return material && material->Map(MapType::Normal) != nullptr;
}
ClearValue SurfaceBase::MakeClearValue(PAddr copy_addr, PixelFormat dst_format) {
const SurfaceType dst_type = GetFormatType(dst_format);
const std::array fill_buffer = MakeFillBuffer(copy_addr);
ClearValue result{};
switch (dst_type) {
case SurfaceType::Color:
case SurfaceType::Texture:
case SurfaceType::Fill: {
Pica::Texture::TextureInfo tex_info{};
tex_info.format = static_cast<Pica::TexturingRegs::TextureFormat>(dst_format);
const auto color = Pica::Texture::LookupTexture(fill_buffer.data(), 0, 0, tex_info);
result.color = color / 255.f;
break;
}
case SurfaceType::Depth: {
u32 depth_uint = 0;
if (dst_format == PixelFormat::D16) {
std::memcpy(&depth_uint, fill_buffer.data(), 2);
result.depth = depth_uint / 65535.0f; // 2^16 - 1
} else if (dst_format == PixelFormat::D24) {
std::memcpy(&depth_uint, fill_buffer.data(), 3);
result.depth = depth_uint / 16777215.0f; // 2^24 - 1
}
break;
}
case SurfaceType::DepthStencil: {
u32 clear_value_uint;
std::memcpy(&clear_value_uint, fill_buffer.data(), sizeof(u32));
result.depth = (clear_value_uint & 0xFFFFFF) / 16777215.0f; // 2^24 - 1
result.stencil = (clear_value_uint >> 24);
break;
}
default:
UNREACHABLE_MSG("Invalid surface type!");
}
return result;
}
std::array<u8, 4> SurfaceBase::MakeFillBuffer(PAddr copy_addr) {
const PAddr fill_offset = (copy_addr - addr) % fill_size;
std::array<u8, 4> fill_buffer;
u32 fill_buff_pos = fill_offset;
for (std::size_t i = 0; i < fill_buffer.size(); i++) {
fill_buffer[i] = fill_data[fill_buff_pos++ % fill_size];
}
return fill_buffer;
}
} // namespace VideoCore