// 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 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(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 SurfaceBase::MakeFillBuffer(PAddr copy_addr) { const PAddr fill_offset = (copy_addr - addr) % fill_size; std::array 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