mirror of
https://github.com/Lime3DS/Lime3DS.git
synced 2026-07-11 02:14:43 -06:00
Merge 99d3abc80e into 209173fa0c
This commit is contained in:
commit
e6d8bee650
@ -93,6 +93,15 @@ if (BUNDLE_TARGET_EXECUTE)
|
||||
message(FATAL_ERROR "macdeployqt failed: ${macdeployqt_result}")
|
||||
endif()
|
||||
|
||||
# Some local filesystem providers attach FinderInfo/resource-fork xattrs during copy,
|
||||
# which causes codesign to reject the bundle.
|
||||
find_program(xattr_executable xattr REQUIRED)
|
||||
execute_process(COMMAND find "${executable_path}" -exec "${xattr_executable}" -c "{}" "+"
|
||||
RESULT_VARIABLE xattr_result)
|
||||
if (NOT xattr_result EQUAL "0")
|
||||
message(FATAL_ERROR "xattr cleanup failed: ${xattr_result}")
|
||||
endif()
|
||||
|
||||
# Bundling libraries can rewrite path information and break code signatures of system libraries.
|
||||
# Perform an ad-hoc re-signing on the whole app bundle to fix this.
|
||||
execute_process(COMMAND codesign --deep -fs - "${executable_path}"
|
||||
|
||||
@ -85,6 +85,11 @@ public:
|
||||
return values_capacity - free_list.size();
|
||||
}
|
||||
|
||||
[[nodiscard]] bool contains(SlotId id) const noexcept {
|
||||
return id && id.index / 64 < stored_bitset.size() &&
|
||||
((stored_bitset[id.index / 64] >> (id.index % 64)) & 1) != 0;
|
||||
}
|
||||
|
||||
private:
|
||||
struct NonTrivialDummy {
|
||||
NonTrivialDummy() noexcept {}
|
||||
@ -111,9 +116,7 @@ private:
|
||||
}
|
||||
|
||||
void ValidateIndex([[maybe_unused]] SlotId id) const noexcept {
|
||||
DEBUG_ASSERT(id);
|
||||
DEBUG_ASSERT(id.index / 64 < stored_bitset.size());
|
||||
DEBUG_ASSERT(((stored_bitset[id.index / 64] >> (id.index % 64)) & 1) != 0);
|
||||
DEBUG_ASSERT(contains(id));
|
||||
}
|
||||
|
||||
[[nodiscard]] u32 FreeValueIndex() noexcept {
|
||||
|
||||
@ -2,6 +2,7 @@ add_executable(tests
|
||||
common/bit_field.cpp
|
||||
common/file_util.cpp
|
||||
common/param_package.cpp
|
||||
common/slot_vector.cpp
|
||||
core/core_timing.cpp
|
||||
core/file_sys/path_parser.cpp
|
||||
core/hle/kernel/hle_ipc.cpp
|
||||
|
||||
24
src/tests/common/slot_vector.cpp
Normal file
24
src/tests/common/slot_vector.cpp
Normal file
@ -0,0 +1,24 @@
|
||||
// Copyright 2026 Azahar Emulator Project
|
||||
// Licensed under GPLv2 or any later version.
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include "common/slot_vector.h"
|
||||
|
||||
TEST_CASE("SlotVector contains", "[common]") {
|
||||
Common::SlotVector<int> slots;
|
||||
const Common::SlotId invalid{};
|
||||
const Common::SlotId out_of_range{128};
|
||||
|
||||
REQUIRE_FALSE(slots.contains(invalid));
|
||||
REQUIRE_FALSE(slots.contains(out_of_range));
|
||||
|
||||
const Common::SlotId first = slots.insert(1);
|
||||
const Common::SlotId second = slots.insert(2);
|
||||
REQUIRE(slots.contains(first));
|
||||
REQUIRE(slots.contains(second));
|
||||
|
||||
slots.erase(first);
|
||||
REQUIRE_FALSE(slots.contains(first));
|
||||
REQUIRE(slots.contains(second));
|
||||
}
|
||||
@ -2,6 +2,7 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <charconv>
|
||||
#include <json.hpp>
|
||||
#include "common/file_util.h"
|
||||
#include "common/literals.h"
|
||||
@ -124,7 +125,7 @@ void CustomTexManager::FindCustomTextures() {
|
||||
|
||||
bool CustomTexManager::ParseFilename(const FileUtil::FSTEntry& file, CustomTexture* texture) {
|
||||
auto parts = Common::SplitString(file.virtualName, '.');
|
||||
if (parts.size() > 3) {
|
||||
if (parts.empty() || parts.size() > 3) {
|
||||
LOG_ERROR(Render, "Invalid filename {}, ignoring", file.virtualName);
|
||||
return false;
|
||||
}
|
||||
@ -139,6 +140,10 @@ bool CustomTexManager::ParseFilename(const FileUtil::FSTEntry& file, CustomTextu
|
||||
}
|
||||
texture->file_format = file_format;
|
||||
parts.pop_back();
|
||||
if (parts.empty()) {
|
||||
LOG_ERROR(Render, "Invalid filename {}, ignoring", file.virtualName);
|
||||
return false;
|
||||
}
|
||||
|
||||
// This means the texture is a material type other than color.
|
||||
texture->type = MapType::Color;
|
||||
@ -169,6 +174,10 @@ bool CustomTexManager::ParseFilename(const FileUtil::FSTEntry& file, CustomTextu
|
||||
}
|
||||
|
||||
texture->path = file.physicalName;
|
||||
if (!texture->IsParsed()) {
|
||||
LOG_ERROR(Render, "Unable to identify custom texture {}, ignoring", file.virtualName);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -299,6 +308,9 @@ Material* CustomTexManager::GetMaterial(u64 data_hash) {
|
||||
bool CustomTexManager::Decode(Material* material, std::function<bool()>&& upload) {
|
||||
if (!async_custom_loading) {
|
||||
material->LoadFromDisk(flip_png_files);
|
||||
if (!material->IsDecoded()) {
|
||||
return false;
|
||||
}
|
||||
return upload();
|
||||
}
|
||||
if (material->IsUnloaded()) {
|
||||
@ -331,22 +343,56 @@ bool CustomTexManager::ReadConfig(u64 title_id, bool options_only) {
|
||||
return false;
|
||||
}
|
||||
|
||||
nlohmann::json json = nlohmann::json::parse(config, nullptr, false, true);
|
||||
const nlohmann::json json = nlohmann::json::parse(config, nullptr, false, true);
|
||||
if (json.is_discarded() || !json.is_object()) {
|
||||
LOG_ERROR(Render, "Failed to parse custom texture config {}", config_path);
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& options = json["options"];
|
||||
skip_mipmap = options["skip_mipmap"].get<bool>();
|
||||
flip_png_files = options["flip_png_files"].get<bool>();
|
||||
use_new_hash = options["use_new_hash"].get<bool>();
|
||||
const auto options_it = json.find("options");
|
||||
if (options_it == json.end() || !options_it->is_object()) {
|
||||
LOG_ERROR(Render, "Custom texture config {} has no valid options object", config_path);
|
||||
return false;
|
||||
}
|
||||
const auto read_bool = [&](std::string_view key, bool& value) {
|
||||
const auto it = options_it->find(key);
|
||||
if (it == options_it->end() || !it->is_boolean()) {
|
||||
LOG_ERROR(Render, "Custom texture config {} has invalid option {}", config_path, key);
|
||||
return false;
|
||||
}
|
||||
value = it->get<bool>();
|
||||
return true;
|
||||
};
|
||||
bool parsed_skip_mipmap{};
|
||||
bool parsed_flip_png_files{};
|
||||
bool parsed_use_new_hash{};
|
||||
if (!read_bool("skip_mipmap", parsed_skip_mipmap) ||
|
||||
!read_bool("flip_png_files", parsed_flip_png_files) ||
|
||||
!read_bool("use_new_hash", parsed_use_new_hash)) {
|
||||
return false;
|
||||
}
|
||||
skip_mipmap = parsed_skip_mipmap;
|
||||
flip_png_files = parsed_flip_png_files;
|
||||
use_new_hash = parsed_use_new_hash;
|
||||
|
||||
if (options_only) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto& textures = json["textures"];
|
||||
const auto textures_it = json.find("textures");
|
||||
if (textures_it == json.end()) {
|
||||
return true;
|
||||
}
|
||||
if (!textures_it->is_object()) {
|
||||
LOG_ERROR(Render, "Custom texture config {} has an invalid textures object", config_path);
|
||||
return false;
|
||||
}
|
||||
const auto& textures = *textures_it;
|
||||
for (const auto& material : textures.items()) {
|
||||
std::size_t idx{};
|
||||
const u64 hash = std::stoull(material.key(), &idx, 16);
|
||||
if (!idx) {
|
||||
u64 hash{};
|
||||
const std::string& key = material.key();
|
||||
const auto [end, error] = std::from_chars(key.data(), key.data() + key.size(), hash, 16);
|
||||
if (error != std::errc{} || end != key.data() + key.size()) {
|
||||
LOG_ERROR(Render, "Key {} is invalid, skipping", material.key());
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -32,7 +32,7 @@ CustomPixelFormat ToCustomPixelFormat(ddsktx_format format) {
|
||||
return CustomPixelFormat::ASTC8;
|
||||
default:
|
||||
LOG_ERROR(Common, "Unknown dds/ktx pixel format {}", format);
|
||||
return CustomPixelFormat::RGBA8;
|
||||
return CustomPixelFormat::Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,18 +54,27 @@ CustomTexture::CustomTexture(Frontend::ImageInterface& image_interface_)
|
||||
|
||||
CustomTexture::~CustomTexture() = default;
|
||||
|
||||
void CustomTexture::LoadFromDisk(bool flip_png) {
|
||||
bool CustomTexture::LoadFromDisk(bool flip_png) {
|
||||
std::scoped_lock lock{decode_mutex};
|
||||
if (IsLoaded()) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
FileUtil::IOFile file{path, "rb"};
|
||||
if (!file.IsOpen() || file.GetSize() == 0) {
|
||||
LOG_CRITICAL(Render, "Failed to open custom texture: {}", path);
|
||||
return false;
|
||||
}
|
||||
std::vector<u8> input(file.GetSize());
|
||||
if (file.ReadBytes(input.data(), input.size()) != input.size()) {
|
||||
LOG_CRITICAL(Render, "Failed to open custom texture: {}", path);
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
data.clear();
|
||||
width = 0;
|
||||
height = 0;
|
||||
format = CustomPixelFormat::Invalid;
|
||||
switch (file_format) {
|
||||
case CustomFileFormat::PNG:
|
||||
LoadPNG(input, flip_png);
|
||||
@ -77,6 +86,12 @@ void CustomTexture::LoadFromDisk(bool flip_png) {
|
||||
default:
|
||||
LOG_ERROR(Render, "Unknown file format {}", file_format);
|
||||
}
|
||||
if (format == CustomPixelFormat::Invalid || data.empty() || width == 0 || height == 0) {
|
||||
data.clear();
|
||||
format = CustomPixelFormat::Invalid;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void CustomTexture::LoadPNG(std::span<const u8> input, bool flip_png) {
|
||||
@ -100,13 +115,27 @@ void Material::LoadFromDisk(bool flip_png) noexcept {
|
||||
if (IsDecoded()) {
|
||||
return;
|
||||
}
|
||||
for (CustomTexture* const texture : textures) {
|
||||
if (!texture || texture->IsLoaded()) {
|
||||
size = 0;
|
||||
width = 0;
|
||||
height = 0;
|
||||
format = CustomPixelFormat::Invalid;
|
||||
for (std::size_t index = 0; index < textures.size(); index++) {
|
||||
CustomTexture* const texture = textures[index];
|
||||
if (!texture) {
|
||||
continue;
|
||||
}
|
||||
texture->LoadFromDisk(flip_png);
|
||||
const bool loaded = texture->LoadFromDisk(flip_png);
|
||||
size += texture->data.size();
|
||||
LOG_DEBUG(Render, "Loading {} map {}", MapTypeName(texture->type), texture->path);
|
||||
if (!loaded) {
|
||||
LOG_ERROR(Render, "Failed to load {} map {} of material with hash {:#016X}",
|
||||
MapTypeName(texture->type), texture->path, hash);
|
||||
if (texture->type == MapType::Color) {
|
||||
state = DecodeState::Failed;
|
||||
return;
|
||||
}
|
||||
textures[index] = nullptr;
|
||||
}
|
||||
}
|
||||
if (!textures[0]) {
|
||||
LOG_ERROR(Render, "Unable to create material without color texture!");
|
||||
@ -126,16 +155,24 @@ void Material::LoadFromDisk(bool flip_png) noexcept {
|
||||
"which do not match the color texture dimentions {}x{}",
|
||||
MapTypeName(texture->type), texture->path, hash, texture->width,
|
||||
texture->height, width, height);
|
||||
state = DecodeState::Failed;
|
||||
return;
|
||||
if (texture->type == MapType::Color) {
|
||||
state = DecodeState::Failed;
|
||||
return;
|
||||
}
|
||||
textures[static_cast<std::size_t>(texture->type)] = nullptr;
|
||||
continue;
|
||||
}
|
||||
if (texture->format != format) {
|
||||
LOG_ERROR(
|
||||
Render, "{} map {} is stored with {} format which does not match color format {}",
|
||||
MapTypeName(texture->type), texture->path,
|
||||
CustomPixelFormatAsString(texture->format), CustomPixelFormatAsString(format));
|
||||
state = DecodeState::Failed;
|
||||
return;
|
||||
if (texture->type == MapType::Color) {
|
||||
state = DecodeState::Failed;
|
||||
return;
|
||||
}
|
||||
textures[static_cast<std::size_t>(texture->type)] = nullptr;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
state = DecodeState::Decoded;
|
||||
|
||||
@ -37,7 +37,7 @@ public:
|
||||
explicit CustomTexture(Frontend::ImageInterface& image_interface);
|
||||
~CustomTexture();
|
||||
|
||||
void LoadFromDisk(bool flip_png);
|
||||
[[nodiscard]] bool LoadFromDisk(bool flip_png);
|
||||
|
||||
[[nodiscard]] bool IsParsed() const noexcept {
|
||||
return file_format != CustomFileFormat::None && !hashes.empty();
|
||||
@ -55,23 +55,23 @@ private:
|
||||
public:
|
||||
Frontend::ImageInterface& image_interface;
|
||||
std::string path;
|
||||
u32 width;
|
||||
u32 height;
|
||||
u32 width{};
|
||||
u32 height{};
|
||||
std::vector<u64> hashes;
|
||||
std::mutex decode_mutex;
|
||||
CustomPixelFormat format;
|
||||
CustomFileFormat file_format;
|
||||
CustomPixelFormat format{CustomPixelFormat::Invalid};
|
||||
CustomFileFormat file_format{};
|
||||
std::vector<u8> data;
|
||||
MapType type;
|
||||
MapType type{MapType::Color};
|
||||
};
|
||||
|
||||
struct Material {
|
||||
u32 width;
|
||||
u32 height;
|
||||
u64 size;
|
||||
u64 hash;
|
||||
CustomPixelFormat format;
|
||||
std::array<CustomTexture*, MAX_MAPS> textures;
|
||||
u32 width{};
|
||||
u32 height{};
|
||||
u64 size{};
|
||||
u64 hash{};
|
||||
CustomPixelFormat format{CustomPixelFormat::Invalid};
|
||||
std::array<CustomTexture*, MAX_MAPS> textures{};
|
||||
std::atomic<DecodeState> state{};
|
||||
|
||||
void LoadFromDisk(bool flip_png) noexcept;
|
||||
|
||||
@ -594,10 +594,10 @@ SurfaceId RasterizerCache<T>::GetTextureSurface(const Pica::Texture::TextureInfo
|
||||
}
|
||||
const auto [src_surface_id, rect] =
|
||||
GetSurfaceSubRect(params, ScaleMatch::Ignore, true, initial_flags);
|
||||
Surface& src_surface = slot_surfaces[src_surface_id];
|
||||
|
||||
params.res_scale = src_surface.res_scale;
|
||||
params.res_scale = slot_surfaces[src_surface_id].res_scale;
|
||||
SurfaceId tmp_surface_id = CreateSurface(params, initial_flags);
|
||||
Surface& src_surface = slot_surfaces[src_surface_id];
|
||||
Surface& tmp_surface = slot_surfaces[tmp_surface_id];
|
||||
sentenced.emplace_back(tmp_surface_id, frame_tick);
|
||||
|
||||
@ -774,12 +774,15 @@ FramebufferHelper<T> RasterizerCache<T>::GetFramebufferSurfaces(bool using_color
|
||||
ValidateSurface(color_id, boost::icl::first(color_vp_interval),
|
||||
boost::icl::length(color_vp_interval));
|
||||
}
|
||||
depth_surface = depth_id ? &slot_surfaces[depth_id] : nullptr;
|
||||
if (depth_id) {
|
||||
depth_level = depth_surface->LevelOf(depth_params.addr);
|
||||
depth_surface->flags |= SurfaceFlagBits::RenderTarget;
|
||||
ValidateSurface(depth_id, boost::icl::first(depth_vp_interval),
|
||||
boost::icl::length(depth_vp_interval));
|
||||
}
|
||||
color_surface = color_id ? &slot_surfaces[color_id] : nullptr;
|
||||
depth_surface = depth_id ? &slot_surfaces[depth_id] : nullptr;
|
||||
|
||||
const FramebufferParams fb_params = {
|
||||
.color_id = color_id,
|
||||
@ -963,23 +966,23 @@ void RasterizerCache<T>::ValidateSurface(SurfaceId surface_id, PAddr addr, u32 s
|
||||
return;
|
||||
}
|
||||
|
||||
Surface& surface = slot_surfaces[surface_id];
|
||||
const SurfaceInterval validate_interval(addr, addr + size);
|
||||
|
||||
if (surface.type == SurfaceType::Fill) {
|
||||
ASSERT_MSG(surface.IsRegionValid(validate_interval),
|
||||
Surface& initial_surface = slot_surfaces[surface_id];
|
||||
if (initial_surface.type == SurfaceType::Fill) {
|
||||
ASSERT_MSG(initial_surface.IsRegionValid(validate_interval),
|
||||
"Attempted to validate a non-valid fill surface");
|
||||
return;
|
||||
}
|
||||
|
||||
SurfaceRegions validate_regions = surface.invalid_regions & validate_interval;
|
||||
SurfaceRegions validate_regions = initial_surface.invalid_regions & validate_interval;
|
||||
|
||||
if (validate_regions.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto notify_validated = [&](SurfaceInterval interval) {
|
||||
surface.MarkValid(interval);
|
||||
slot_surfaces[surface_id].MarkValid(interval);
|
||||
validate_regions.erase(interval);
|
||||
};
|
||||
|
||||
@ -987,9 +990,12 @@ void RasterizerCache<T>::ValidateSurface(SurfaceId surface_id, PAddr addr, u32 s
|
||||
"RasterizerCache::ValidateSurface (from {:#x} to {:#x})", addr,
|
||||
addr + size};
|
||||
|
||||
u32 level = surface.LevelOf(addr);
|
||||
SurfaceInterval level_interval = surface.LevelInterval(level);
|
||||
u32 level = initial_surface.LevelOf(addr);
|
||||
SurfaceInterval level_interval = initial_surface.LevelInterval(level);
|
||||
while (!validate_regions.empty()) {
|
||||
// UploadCustomSurface may grow slot_surfaces and invalidate references to its entries.
|
||||
Surface& surface = slot_surfaces[surface_id];
|
||||
|
||||
// Take an invalid interval from the validation regions and clamp it
|
||||
// to the current level interval. If the interval is empty
|
||||
// then we have validated the entire level so move to the next.
|
||||
@ -1020,13 +1026,14 @@ void RasterizerCache<T>::ValidateSurface(SurfaceId surface_id, PAddr addr, u32 s
|
||||
|
||||
FlushRegion(params.addr, params.size);
|
||||
if (!use_custom_textures || !UploadCustomSurface(surface_id, interval)) {
|
||||
UploadSurface(surface, interval);
|
||||
UploadSurface(slot_surfaces[surface_id], interval);
|
||||
}
|
||||
notify_validated(params.GetInterval());
|
||||
}
|
||||
|
||||
// Filtered mipmaps often look really bad. We can achieve better quality by
|
||||
// generating them from the base level.
|
||||
Surface& surface = slot_surfaces[surface_id];
|
||||
if (surface.res_scale != 1 && level != 0) {
|
||||
runtime.GenerateMipmaps(surface);
|
||||
}
|
||||
@ -1109,22 +1116,42 @@ bool RasterizerCache<T>::UploadCustomSurface(SurfaceId surface_id, SurfaceInterv
|
||||
return true;
|
||||
}
|
||||
|
||||
surface.flags |= SurfaceFlagBits::Custom;
|
||||
const SurfaceParams expected_surface = surface;
|
||||
|
||||
const auto upload = [this, level, surface_id, material]() -> bool {
|
||||
ASSERT_MSG(True(slot_surfaces[surface_id].flags & SurfaceFlagBits::Custom),
|
||||
"Surface is not suitable for custom upload, aborting!");
|
||||
if (!slot_surfaces[surface_id].IsCustom()) {
|
||||
const SurfaceBase old_surface{slot_surfaces[surface_id]};
|
||||
const auto upload = [this, level, surface_id, material, expected_surface, load_info,
|
||||
hash]() -> bool {
|
||||
if (!slot_surfaces.contains(surface_id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Surface& target = slot_surfaces[surface_id];
|
||||
if (False(target.flags & SurfaceFlagBits::Registered) ||
|
||||
!(static_cast<const SurfaceParams&>(target) == expected_surface) ||
|
||||
!runtime.SupportsCustomFormat(material->format)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MemoryRef source_ptr = memory.GetPhysicalRef(load_info.addr);
|
||||
if (!source_ptr) {
|
||||
return false;
|
||||
}
|
||||
const auto upload_data = source_ptr.GetWriteBytes(load_info.end - load_info.addr);
|
||||
if (ComputeHash(load_info, upload_data) != hash) {
|
||||
return false;
|
||||
}
|
||||
|
||||
target.flags |= SurfaceFlagBits::Custom;
|
||||
if (!target.IsCustom() || target.material != material) {
|
||||
const SurfaceBase old_surface{target};
|
||||
const SurfaceId old_id =
|
||||
slot_surfaces.swap_and_insert(surface_id, runtime, old_surface, material);
|
||||
slot_surfaces[old_id].flags &= ~SurfaceFlagBits::Registered;
|
||||
sentenced.emplace_back(old_id, frame_tick);
|
||||
}
|
||||
Surface& surface = slot_surfaces[surface_id];
|
||||
surface.UploadCustom(material, level);
|
||||
Surface& custom_surface = slot_surfaces[surface_id];
|
||||
custom_surface.UploadCustom(material, level);
|
||||
if (custom_tex_manager.SkipMipmaps()) {
|
||||
runtime.GenerateMipmaps(surface);
|
||||
runtime.GenerateMipmaps(custom_surface);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
@ -1166,6 +1193,9 @@ void RasterizerCache<T>::DownloadFillSurface(Surface& surface, SurfaceInterval i
|
||||
const u32 flush_start = boost::icl::first(interval);
|
||||
const u32 flush_end = boost::icl::last_next(interval);
|
||||
ASSERT(flush_start >= surface.addr && flush_end <= surface.end);
|
||||
if (surface.fill_size == 0 || surface.fill_size > surface.fill_data.size()) [[unlikely]] {
|
||||
return;
|
||||
}
|
||||
|
||||
MemoryRef dest_ptr = memory.GetPhysicalRef(flush_start);
|
||||
if (!dest_ptr) [[unlikely]] {
|
||||
@ -1175,21 +1205,9 @@ void RasterizerCache<T>::DownloadFillSurface(Surface& surface, SurfaceInterval i
|
||||
const u32 start_offset = flush_start - surface.addr;
|
||||
const u32 download_size =
|
||||
std::clamp(flush_end - flush_start, 0u, static_cast<u32>(dest_ptr.GetSize()));
|
||||
const u32 coarse_start_offset = start_offset - (start_offset % surface.fill_size);
|
||||
const u32 backup_bytes = start_offset % surface.fill_size;
|
||||
|
||||
std::array<u8, 4> backup_data;
|
||||
if (backup_bytes) {
|
||||
std::memcpy(backup_data.data(), &dest_ptr[coarse_start_offset], backup_bytes);
|
||||
}
|
||||
|
||||
for (u32 offset = coarse_start_offset; offset < download_size; offset += surface.fill_size) {
|
||||
std::memcpy(&dest_ptr[offset], &surface.fill_data[0],
|
||||
std::min(surface.fill_size, download_size - offset));
|
||||
}
|
||||
|
||||
if (backup_bytes) {
|
||||
std::memcpy(&dest_ptr[coarse_start_offset], &backup_data[0], backup_bytes);
|
||||
const u32 fill_start = start_offset % surface.fill_size;
|
||||
for (u32 offset = 0; offset < download_size; ++offset) {
|
||||
dest_ptr[offset] = surface.fill_data[(fill_start + offset) % surface.fill_size];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -8,6 +8,13 @@
|
||||
#include "video_core/texture/texture_decode.h"
|
||||
|
||||
namespace VideoCore {
|
||||
namespace {
|
||||
|
||||
bool IsValidFillSize(u32 fill_size) {
|
||||
return fill_size != 0 && fill_size <= sizeof(SurfaceBase::fill_data);
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
SurfaceBase::SurfaceBase(const SurfaceParams& params, const SurfaceFlagBits& initial_flag_bits)
|
||||
: SurfaceParams{params}, flags(initial_flag_bits) {}
|
||||
@ -20,6 +27,9 @@ bool SurfaceBase::CanFill(const SurfaceParams& dest_surface, SurfaceInterval fil
|
||||
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 (!IsValidFillSize(fill_size)) {
|
||||
return false;
|
||||
}
|
||||
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);
|
||||
@ -130,6 +140,7 @@ bool SurfaceBase::HasNormalMap() const noexcept {
|
||||
ClearValue SurfaceBase::MakeClearValue(PAddr copy_addr, PixelFormat dst_format) {
|
||||
const SurfaceType dst_type = GetFormatType(dst_format);
|
||||
const std::array fill_buffer = MakeFillBuffer(copy_addr);
|
||||
const bool valid_fill = IsValidFillSize(fill_size);
|
||||
|
||||
ClearValue result{};
|
||||
switch (dst_type) {
|
||||
@ -138,7 +149,13 @@ ClearValue SurfaceBase::MakeClearValue(PAddr copy_addr, PixelFormat dst_format)
|
||||
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);
|
||||
const std::size_t tile_size = Pica::Texture::CalculateTileSize(tex_info.format);
|
||||
std::vector<u8> fill_tile(std::max(tile_size, fill_buffer.size()));
|
||||
for (std::size_t i = 0; i < fill_tile.size(); i++) {
|
||||
fill_tile[i] = valid_fill ? fill_data[(copy_addr - addr + i) % fill_size]
|
||||
: fill_buffer[i % fill_buffer.size()];
|
||||
}
|
||||
const auto color = Pica::Texture::LookupTexture(fill_tile.data(), 0, 0, tex_info);
|
||||
result.color = color / 255.f;
|
||||
break;
|
||||
}
|
||||
@ -168,9 +185,12 @@ ClearValue SurfaceBase::MakeClearValue(PAddr copy_addr, PixelFormat dst_format)
|
||||
}
|
||||
|
||||
std::array<u8, 4> SurfaceBase::MakeFillBuffer(PAddr copy_addr) {
|
||||
const PAddr fill_offset = (copy_addr - addr) % fill_size;
|
||||
std::array<u8, 4> fill_buffer;
|
||||
std::array<u8, 4> fill_buffer{};
|
||||
if (!IsValidFillSize(fill_size)) {
|
||||
return fill_buffer;
|
||||
}
|
||||
|
||||
const PAddr fill_offset = (copy_addr - addr) % fill_size;
|
||||
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];
|
||||
|
||||
@ -407,8 +407,8 @@ static constexpr std::array<MortonFunc, 18> UNSWIZZLE_TABLE_CONVERTED = {
|
||||
nullptr, // 9
|
||||
nullptr, // 10
|
||||
nullptr, // 11
|
||||
nullptr, // 12
|
||||
nullptr, // 13
|
||||
MortonCopy<true, PixelFormat::ETC1, true>, // 12
|
||||
MortonCopy<true, PixelFormat::ETC1A4, true>, // 13
|
||||
MortonCopy<true, PixelFormat::D16, true>, // 14
|
||||
nullptr, // 15
|
||||
MortonCopy<true, PixelFormat::D24, true>, // 16
|
||||
|
||||
@ -58,6 +58,10 @@ public:
|
||||
const FormatTuple& GetFormatTuple(VideoCore::PixelFormat pixel_format) const;
|
||||
const FormatTuple& GetFormatTuple(VideoCore::CustomPixelFormat pixel_format);
|
||||
|
||||
bool SupportsCustomFormat(VideoCore::CustomPixelFormat pixel_format) const {
|
||||
return driver.IsCustomFormatSupported(pixel_format);
|
||||
}
|
||||
|
||||
/// Attempts to reinterpret a rectangle of source to another rectangle of dest
|
||||
bool Reinterpret(Surface& source, Surface& dest, const VideoCore::TextureCopy& copy);
|
||||
|
||||
|
||||
@ -829,9 +829,10 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceBase& surface
|
||||
raw_images[num_images++] = handles[Type::Scaled].image;
|
||||
}
|
||||
if (has_normal) {
|
||||
handles[Type::Custom].Create(mat->width, mat->height, levels, texture_type, format,
|
||||
traits.usage, flags, traits.aspect, false, debug_name);
|
||||
raw_images[num_images++] = handles[Type::Custom].image;
|
||||
LOG_WARNING(Render_Vulkan,
|
||||
"Ignoring custom normal map for material hash {:#016X}: Vulkan does not bind "
|
||||
"custom normal maps yet",
|
||||
mat->hash);
|
||||
}
|
||||
|
||||
current = res_scale != 1 ? Type::Scaled : Type::Base;
|
||||
@ -1006,8 +1007,11 @@ void Surface::UploadCustom(const VideoCore::Material* material, u32 level) {
|
||||
};
|
||||
|
||||
upload(Type::Base, color);
|
||||
if (auto* texture = material->textures[u32(MapType::Normal)]) {
|
||||
upload(Type::Custom, texture);
|
||||
if (material->textures[u32(MapType::Normal)]) {
|
||||
LOG_DEBUG(Render_Vulkan,
|
||||
"Skipping custom normal map upload for material hash {:#016X}: Vulkan does not "
|
||||
"bind custom normal maps yet",
|
||||
material->hash);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1282,7 +1286,12 @@ vk::ImageView Surface::CopyImageView() noexcept {
|
||||
}
|
||||
|
||||
vk::ImageView Surface::ImageView(ViewType view_type, Type type) noexcept {
|
||||
auto& handle = handles[type == Type::Current ? current : type];
|
||||
const Type resolved_type = type == Type::Current ? current : type;
|
||||
if (!handles[resolved_type] && resolved_type != Type::Base) {
|
||||
return ImageView(view_type, Type::Base);
|
||||
}
|
||||
|
||||
auto& handle = handles[resolved_type];
|
||||
if (auto image_view = handle.image_views[view_type]) {
|
||||
return image_view;
|
||||
}
|
||||
|
||||
@ -117,6 +117,10 @@ public:
|
||||
return instance;
|
||||
}
|
||||
|
||||
bool SupportsCustomFormat(VideoCore::CustomPixelFormat pixel_format) const {
|
||||
return instance.GetTraits(pixel_format).transfer_support;
|
||||
}
|
||||
|
||||
Scheduler& GetScheduler() const {
|
||||
return scheduler;
|
||||
}
|
||||
@ -192,7 +196,12 @@ public:
|
||||
|
||||
/// Returns the image at index, otherwise the base image
|
||||
vk::Image Image(Type type = Type::Current) const noexcept {
|
||||
return handles[type == Type::Current ? current : type].image;
|
||||
const Type resolved_type = type == Type::Current ? current : type;
|
||||
const auto& handle = handles[resolved_type];
|
||||
if (!handle && resolved_type != Type::Base) {
|
||||
return handles[Type::Base].image;
|
||||
}
|
||||
return handle.image;
|
||||
}
|
||||
|
||||
/// Returns the image view at index, otherwise the base view
|
||||
|
||||
Loading…
Reference in New Issue
Block a user