Merge branch 'master' of https://github.com/dolphin-emu/dolphin into dolphin-emu-master

This commit is contained in:
Nayla Hanegan 2023-12-25 20:08:08 -05:00
commit c18016e795
767 changed files with 87644 additions and 70168 deletions

View file

@ -19,7 +19,7 @@ void AbstractTexture::FinishedRendering()
{
}
bool AbstractTexture::Save(const std::string& filename, unsigned int level, int compression)
bool AbstractTexture::Save(const std::string& filename, unsigned int level, int compression) const
{
// We can't dump compressed textures currently (it would mean drawing them to a RGBA8
// framebuffer, and saving that). TextureCache does not call Save for custom textures
@ -37,7 +37,8 @@ bool AbstractTexture::Save(const std::string& filename, unsigned int level, int
// Use a temporary staging texture for the download. Certainly not optimal,
// but this is not a frequently-executed code path..
TextureConfig readback_texture_config(level_width, level_height, 1, 1, 1,
AbstractTextureFormat::RGBA8, 0);
AbstractTextureFormat::RGBA8, 0,
AbstractTextureType::Texture_2DArray);
auto readback_texture =
g_gfx->CreateStagingTexture(StagingTextureType::Readback, readback_texture_config);
if (!readback_texture)

View file

@ -38,7 +38,7 @@ public:
MathUtil::Rectangle<int> GetRect() const { return m_config.GetRect(); }
MathUtil::Rectangle<int> GetMipRect(u32 level) const { return m_config.GetMipRect(level); }
bool IsMultisampled() const { return m_config.IsMultisampled(); }
bool Save(const std::string& filename, unsigned int level, int compression = 6);
bool Save(const std::string& filename, unsigned int level, int compression = 6) const;
static bool IsCompressedFormat(AbstractTextureFormat format);
static bool IsDepthFormat(AbstractTextureFormat format);

View file

@ -6,36 +6,31 @@
#include <algorithm>
#include "Common/Logging/Log.h"
#include "VideoCommon/Assets/CustomTextureData.h"
#include "VideoCommon/Assets/TextureAsset.h"
namespace VideoCommon
{
namespace
{
std::size_t GetAssetSize(const CustomTextureData& data)
{
std::size_t total = 0;
for (const auto& slice : data.m_slices)
{
for (const auto& level : slice.m_levels)
{
total += level.data.size();
}
}
return total;
}
} // namespace
CustomAssetLibrary::LoadInfo CustomAssetLibrary::LoadGameTexture(const AssetID& asset_id,
CustomTextureData* data)
TextureData* data)
{
const auto load_info = LoadTexture(asset_id, data);
if (load_info.m_bytes_loaded == 0)
return {};
// Note: 'LoadTexture()' ensures we have a level loaded
for (std::size_t slice_index = 0; slice_index < data->m_slices.size(); slice_index++)
if (data->m_type != TextureData::Type::Type_Texture2D)
{
auto& slice = data->m_slices[slice_index];
ERROR_LOG_FMT(
VIDEO,
"Custom asset '{}' is not a valid game texture, it is expected to be a 2d texture "
"but was a '{}'.",
asset_id, data->m_type);
return {};
}
// Note: 'LoadTexture()' ensures we have a level loaded
for (std::size_t slice_index = 0; slice_index < data->m_texture.m_slices.size(); slice_index++)
{
auto& slice = data->m_texture.m_slices[slice_index];
const auto& first_mip = slice.m_levels[0];
// Verify that each mip level is the correct size (divide by 2 each time).

View file

@ -10,9 +10,9 @@
namespace VideoCommon
{
class CustomTextureData;
struct MaterialData;
struct PixelShaderData;
struct TextureData;
// This class provides functionality to load
// specific data (like textures). Where this data
@ -28,18 +28,20 @@ public:
struct LoadInfo
{
std::size_t m_bytes_loaded = 0;
CustomAssetLibrary::TimeType m_load_time = {};
TimeType m_load_time = {};
};
virtual ~CustomAssetLibrary() = default;
// Loads a texture, if there are no levels, bytes loaded will be empty
virtual LoadInfo LoadTexture(const AssetID& asset_id, CustomTextureData* data) = 0;
virtual LoadInfo LoadTexture(const AssetID& asset_id, TextureData* data) = 0;
// Gets the last write time for a given asset id
virtual TimeType GetLastAssetWriteTime(const AssetID& asset_id) const = 0;
// Loads a texture as a game texture, providing additional checks like confirming
// each mip level size is correct and that the format is consistent across the data
LoadInfo LoadGameTexture(const AssetID& asset_id, CustomTextureData* data);
LoadInfo LoadGameTexture(const AssetID& asset_id, TextureData* data);
// Loads a pixel shader
virtual LoadInfo LoadPixelShader(const AssetID& asset_id, PixelShaderData* data) = 0;

View file

@ -77,13 +77,6 @@ void CustomAssetLoader ::Shutdown()
m_total_bytes_loaded = 0;
}
std::shared_ptr<RawTextureAsset>
CustomAssetLoader::LoadTexture(const CustomAssetLibrary::AssetID& asset_id,
std::shared_ptr<CustomAssetLibrary> library)
{
return LoadOrCreateAsset<RawTextureAsset>(asset_id, m_textures, std::move(library));
}
std::shared_ptr<GameTextureAsset>
CustomAssetLoader::LoadGameTexture(const CustomAssetLibrary::AssetID& asset_id,
std::shared_ptr<CustomAssetLibrary> library)

View file

@ -38,9 +38,6 @@ public:
// Loads happen asynchronously where the data will be set now or in the future
// Callees are expected to query the underlying data with 'GetData()'
// from the 'CustomLoadableAsset' class to determine if the data is ready for use
std::shared_ptr<RawTextureAsset> LoadTexture(const CustomAssetLibrary::AssetID& asset_id,
std::shared_ptr<CustomAssetLibrary> library);
std::shared_ptr<GameTextureAsset> LoadGameTexture(const CustomAssetLibrary::AssetID& asset_id,
std::shared_ptr<CustomAssetLibrary> library);
@ -80,7 +77,6 @@ private:
static constexpr auto TIME_BETWEEN_ASSET_MONITOR_CHECKS = std::chrono::milliseconds{500};
std::map<CustomAssetLibrary::AssetID, std::weak_ptr<RawTextureAsset>> m_textures;
std::map<CustomAssetLibrary::AssetID, std::weak_ptr<GameTextureAsset>> m_game_textures;
std::map<CustomAssetLibrary::AssetID, std::weak_ptr<PixelShaderAsset>> m_pixel_shaders;
std::map<CustomAssetLibrary::AssetID, std::weak_ptr<MaterialAsset>> m_materials;

View file

@ -12,6 +12,7 @@
#include "Common/StringUtil.h"
#include "VideoCommon/Assets/MaterialAsset.h"
#include "VideoCommon/Assets/ShaderAsset.h"
#include "VideoCommon/Assets/TextureAsset.h"
namespace VideoCommon
{
@ -219,66 +220,126 @@ CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadMaterial(const As
}
CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadTexture(const AssetID& asset_id,
CustomTextureData* data)
TextureData* data)
{
const auto asset_map = GetAssetMapForID(asset_id);
// Raw texture is expected to have one asset mapped
if (asset_map.empty() || asset_map.size() > 1)
// Texture can optionally have a metadata file as well
if (asset_map.empty() || asset_map.size() > 2)
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' error - raw texture expected to have one file mapped!",
ERROR_LOG_FMT(VIDEO, "Asset '{}' error - raw texture expected to have one or two files mapped!",
asset_id);
return {};
}
const auto& asset_path = asset_map.begin()->second;
std::error_code ec;
const auto last_loaded_time = std::filesystem::last_write_time(asset_path, ec);
if (ec)
const auto metadata = asset_map.find("metadata");
const auto texture_path = asset_map.find("texture");
if (texture_path == asset_map.end())
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' error - failed to get last write time with error '{}'!",
asset_id, ec);
ERROR_LOG_FMT(VIDEO, "Asset '{}' expected to have a texture entry mapped!", asset_id);
return {};
}
auto ext = PathToString(asset_path.extension());
std::size_t metadata_size = 0;
if (metadata != asset_map.end())
{
std::error_code ec;
metadata_size = std::filesystem::file_size(metadata->second, ec);
if (ec)
{
ERROR_LOG_FMT(VIDEO,
"Asset '{}' error - failed to get texture metadata file size with error '{}'!",
asset_id, ec);
return {};
}
std::string json_data;
if (!File::ReadFileToString(PathToString(metadata->second), json_data))
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' error - failed to load the json file '{}',", asset_id,
PathToString(metadata->second));
return {};
}
picojson::value root;
const auto error = picojson::parse(root, json_data);
if (!error.empty())
{
ERROR_LOG_FMT(VIDEO,
"Asset '{}' error - failed to load the json file '{}', due to parse error: {}",
asset_id, PathToString(metadata->second), error);
return {};
}
if (!root.is<picojson::object>())
{
ERROR_LOG_FMT(
VIDEO,
"Asset '{}' error - failed to load the json file '{}', due to root not being an object!",
asset_id, PathToString(metadata->second));
return {};
}
const auto& root_obj = root.get<picojson::object>();
if (!TextureData::FromJson(asset_id, root_obj, data))
{
return {};
}
}
else
{
data->m_type = TextureData::Type::Type_Texture2D;
}
auto ext = PathToString(texture_path->second.extension());
Common::ToLower(&ext);
if (ext == ".dds")
{
if (!LoadDDSTexture(data, PathToString(asset_path)))
if (!LoadDDSTexture(&data->m_texture, PathToString(texture_path->second)))
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' error - could not load dds texture!", asset_id);
return {};
}
if (data->m_slices.empty()) [[unlikely]]
data->m_slices.push_back({});
if (data->m_texture.m_slices.empty()) [[unlikely]]
data->m_texture.m_slices.push_back({});
if (!LoadMips(asset_path, &data->m_slices[0]))
if (!LoadMips(texture_path->second, &data->m_texture.m_slices[0]))
return {};
return LoadInfo{GetAssetSize(*data), FileTimeToSysTime(last_loaded_time)};
return LoadInfo{GetAssetSize(data->m_texture) + metadata_size, GetLastAssetWriteTime(asset_id)};
}
else if (ext == ".png")
{
// If we have no slices, create one
if (data->m_slices.empty())
data->m_slices.push_back({});
// PNG could support more complicated texture types in the future
// but for now just error
if (data->m_type != TextureData::Type::Type_Texture2D)
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' error - PNG is not supported for texture type '{}'!",
asset_id, data->m_type);
return {};
}
auto& slice = data->m_slices[0];
// If we have no slices, create one
if (data->m_texture.m_slices.empty())
data->m_texture.m_slices.push_back({});
auto& slice = data->m_texture.m_slices[0];
// If we have no levels, create one to pass into LoadPNGTexture
if (slice.m_levels.empty())
slice.m_levels.push_back({});
if (!LoadPNGTexture(&slice.m_levels[0], PathToString(asset_path)))
if (!LoadPNGTexture(&slice.m_levels[0], PathToString(texture_path->second)))
{
ERROR_LOG_FMT(VIDEO, "Asset '{}' error - could not load png texture!", asset_id);
return {};
}
if (!LoadMips(asset_path, &slice))
if (!LoadMips(texture_path->second, &slice))
return {};
return LoadInfo{GetAssetSize(*data), FileTimeToSysTime(last_loaded_time)};
return LoadInfo{GetAssetSize(data->m_texture) + metadata_size, GetLastAssetWriteTime(asset_id)};
}
ERROR_LOG_FMT(VIDEO, "Asset '{}' error - extension '{}' unknown!", asset_id, ext);

View file

@ -20,7 +20,7 @@ class DirectFilesystemAssetLibrary final : public CustomAssetLibrary
public:
using AssetMap = std::map<std::string, std::filesystem::path>;
LoadInfo LoadTexture(const AssetID& asset_id, CustomTextureData* data) override;
LoadInfo LoadTexture(const AssetID& asset_id, TextureData* data) override;
LoadInfo LoadPixelShader(const AssetID& asset_id, PixelShaderData* data) override;
LoadInfo LoadMaterial(const AssetID& asset_id, MaterialData* data) override;

View file

@ -3,32 +3,137 @@
#include "VideoCommon/Assets/MaterialAsset.h"
#include <algorithm>
#include <string_view>
#include <vector>
#include "Common/Logging/Log.h"
#include "Common/StringUtil.h"
#include "Common/VariantUtil.h"
#include "VideoCommon/Assets/CustomAssetLibrary.h"
#include "VideoCommon/ShaderGenCommon.h"
namespace VideoCommon
{
namespace
{
bool ParsePropertyValue(const CustomAssetLibrary::AssetID& asset_id, MaterialProperty::Type type,
const picojson::value& json_value,
std::optional<MaterialProperty::Value>* value)
// While not optimal, we pad our data to match std140 shader requirements
// this memory constant indicates the memory stride for a single uniform
// regardless of data type
constexpr std::size_t MemorySize = sizeof(float) * 4;
template <typename ElementType, std::size_t ElementCount>
bool ParseNumeric(const CustomAssetLibrary::AssetID& asset_id, const picojson::value& json_value,
std::string_view code_name, MaterialProperty::Value* value)
{
switch (type)
static_assert(ElementCount <= 4, "Numeric data expected to be four elements or less");
if constexpr (ElementCount == 1)
{
case MaterialProperty::Type::Type_TextureAsset:
{
if (json_value.is<std::string>())
if (!json_value.is<double>())
{
*value = json_value.to_str();
ERROR_LOG_FMT(VIDEO,
"Asset id '{}' material has attribute '{}' where "
"a double was expected but not provided.",
asset_id, code_name);
return false;
}
*value = static_cast<ElementType>(json_value.get<double>());
}
else
{
if (!json_value.is<picojson::array>())
{
ERROR_LOG_FMT(VIDEO,
"Asset id '{}' material has attribute '{}' where "
"an array was expected but not provided.",
asset_id, code_name);
return false;
}
const auto json_data = json_value.get<picojson::array>();
if (json_data.size() != ElementCount)
{
ERROR_LOG_FMT(VIDEO,
"Asset id '{}' material has attribute '{}' with incorrect number "
"of elements, expected {}",
asset_id, code_name, ElementCount);
return false;
}
if (!std::all_of(json_data.begin(), json_data.end(),
[](const picojson::value& v) { return v.is<double>(); }))
{
ERROR_LOG_FMT(VIDEO,
"Asset id '{}' material has attribute '{}' where "
"all elements are not of type double.",
asset_id, code_name);
return false;
}
std::array<ElementType, ElementCount> data;
for (std::size_t i = 0; i < ElementCount; i++)
{
data[i] = static_cast<ElementType>(json_data[i].get<double>());
}
*value = std::move(data);
}
return true;
}
bool ParsePropertyValue(const CustomAssetLibrary::AssetID& asset_id,
const picojson::value& json_value, std::string_view code_name,
std::string_view type, MaterialProperty::Value* value)
{
if (type == "int")
{
return ParseNumeric<s32, 1>(asset_id, json_value, code_name, value);
}
else if (type == "int2")
{
return ParseNumeric<s32, 2>(asset_id, json_value, code_name, value);
}
else if (type == "int3")
{
return ParseNumeric<s32, 3>(asset_id, json_value, code_name, value);
}
else if (type == "int4")
{
return ParseNumeric<s32, 4>(asset_id, json_value, code_name, value);
}
else if (type == "float")
{
return ParseNumeric<float, 1>(asset_id, json_value, code_name, value);
}
else if (type == "float2")
{
return ParseNumeric<float, 2>(asset_id, json_value, code_name, value);
}
else if (type == "float3")
{
return ParseNumeric<float, 3>(asset_id, json_value, code_name, value);
}
else if (type == "float4")
{
return ParseNumeric<float, 4>(asset_id, json_value, code_name, value);
}
else if (type == "bool")
{
if (json_value.is<bool>())
{
*value = json_value.get<bool>();
return true;
}
}
else if (type == "texture_asset")
{
if (json_value.is<std::string>())
{
*value = json_value.get<std::string>();
return true;
}
}
break;
};
ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse the json, value is not valid for type '{}'",
asset_id, type);
@ -67,18 +172,6 @@ bool ParseMaterialProperties(const CustomAssetLibrary::AssetID& asset_id,
}
std::string type = type_iter->second.to_str();
Common::ToLower(&type);
if (type == "texture_asset")
{
property.m_type = MaterialProperty::Type::Type_TextureAsset;
}
else
{
ERROR_LOG_FMT(VIDEO,
"Asset '{}' failed to parse the json, value entry 'type' is "
"an invalid option",
asset_id);
return false;
}
const auto code_name_iter = value_data_obj.find("code_name");
if (code_name_iter == value_data_obj.end())
@ -102,8 +195,11 @@ bool ParseMaterialProperties(const CustomAssetLibrary::AssetID& asset_id,
const auto value_iter = value_data_obj.find("value");
if (value_iter != value_data_obj.end())
{
if (!ParsePropertyValue(asset_id, property.m_type, value_iter->second, &property.m_value))
if (!ParsePropertyValue(asset_id, value_iter->second, property.m_code_name, type,
&property.m_value))
{
return false;
}
}
material_property->push_back(std::move(property));
@ -111,7 +207,90 @@ bool ParseMaterialProperties(const CustomAssetLibrary::AssetID& asset_id,
return true;
}
template <typename T, std::size_t N>
picojson::array ArrayToPicoArray(const std::array<T, N>& value)
{
picojson::array result;
for (std::size_t i = 0; i < N; i++)
{
result.push_back(picojson::value{static_cast<double>(value[i])});
}
return result;
}
} // namespace
void MaterialProperty::WriteToMemory(u8*& buffer, const MaterialProperty& property)
{
const auto write_memory = [&](const void* raw_value, std::size_t data_size) {
std::memcpy(buffer, raw_value, data_size);
std::memset(buffer + data_size, 0, MemorySize - data_size);
buffer += MemorySize;
};
std::visit(
overloaded{
[&](const CustomAssetLibrary::AssetID&) {},
[&](s32 value) { write_memory(&value, sizeof(s32)); },
[&](const std::array<s32, 2>& value) { write_memory(value.data(), sizeof(s32) * 2); },
[&](const std::array<s32, 3>& value) { write_memory(value.data(), sizeof(s32) * 3); },
[&](const std::array<s32, 4>& value) { write_memory(value.data(), sizeof(s32) * 4); },
[&](float value) { write_memory(&value, sizeof(float)); },
[&](const std::array<float, 2>& value) { write_memory(value.data(), sizeof(float) * 2); },
[&](const std::array<float, 3>& value) { write_memory(value.data(), sizeof(float) * 3); },
[&](const std::array<float, 4>& value) { write_memory(value.data(), sizeof(float) * 4); },
[&](bool value) { write_memory(&value, sizeof(bool)); }},
property.m_value);
}
std::size_t MaterialProperty::GetMemorySize(const MaterialProperty& property)
{
std::size_t result = 0;
std::visit(overloaded{[&](const CustomAssetLibrary::AssetID&) {},
[&](s32) { result = MemorySize; },
[&](const std::array<s32, 2>&) { result = MemorySize; },
[&](const std::array<s32, 3>&) { result = MemorySize; },
[&](const std::array<s32, 4>&) { result = MemorySize; },
[&](float) { result = MemorySize; },
[&](const std::array<float, 2>&) { result = MemorySize; },
[&](const std::array<float, 3>&) { result = MemorySize; },
[&](const std::array<float, 4>&) { result = MemorySize; },
[&](bool) { result = MemorySize; }},
property.m_value);
return result;
}
void MaterialProperty::WriteAsShaderCode(ShaderCode& shader_source,
const MaterialProperty& property)
{
const auto write_shader = [&](std::string_view type, u32 element_count) {
if (element_count == 1)
{
shader_source.Write("{} {};\n", type, property.m_code_name);
}
else
{
shader_source.Write("{}{} {};\n", type, element_count, property.m_code_name);
}
for (std::size_t i = element_count; i < 4; i++)
{
shader_source.Write("{} {}_padding_{};\n", type, property.m_code_name, i + 1);
}
};
std::visit(overloaded{[&](const CustomAssetLibrary::AssetID&) {},
[&](s32 value) { write_shader("int", 1); },
[&](const std::array<s32, 2>& value) { write_shader("int", 2); },
[&](const std::array<s32, 3>& value) { write_shader("int", 3); },
[&](const std::array<s32, 4>& value) { write_shader("int", 4); },
[&](float value) { write_shader("float", 1); },
[&](const std::array<float, 2>& value) { write_shader("float", 2); },
[&](const std::array<float, 3>& value) { write_shader("float", 3); },
[&](const std::array<float, 4>& value) { write_shader("float", 4); },
[&](bool value) { write_shader("bool", 1); }},
property.m_value);
}
bool MaterialData::FromJson(const CustomAssetLibrary::AssetID& asset_id,
const picojson::object& json, MaterialData* data)
{

View file

@ -3,7 +3,7 @@
#pragma once
#include <optional>
#include <array>
#include <string>
#include <variant>
#include <vector>
@ -14,20 +14,20 @@
#include "Common/EnumFormatter.h"
#include "VideoCommon/Assets/CustomAsset.h"
class ShaderCode;
namespace VideoCommon
{
struct MaterialProperty
{
using Value = std::variant<std::string>;
enum class Type
{
Type_Undefined,
Type_TextureAsset,
Type_Max = Type_TextureAsset
};
static void WriteToMemory(u8*& buffer, const MaterialProperty& property);
static std::size_t GetMemorySize(const MaterialProperty& property);
static void WriteAsShaderCode(ShaderCode& shader_source, const MaterialProperty& property);
using Value = std::variant<CustomAssetLibrary::AssetID, s32, std::array<s32, 2>,
std::array<s32, 3>, std::array<s32, 4>, float, std::array<float, 2>,
std::array<float, 3>, std::array<float, 4>, bool>;
std::string m_code_name;
Type m_type = Type::Type_Undefined;
std::optional<Value> m_value;
Value m_value;
};
struct MaterialData
@ -52,10 +52,3 @@ private:
};
} // namespace VideoCommon
template <>
struct fmt::formatter<VideoCommon::MaterialProperty::Type>
: EnumFormatter<VideoCommon::MaterialProperty::Type::Type_Max>
{
constexpr formatter() : EnumFormatter({"Undefined", "Texture"}) {}
};

View file

@ -3,14 +3,177 @@
#include "VideoCommon/Assets/ShaderAsset.h"
#include <algorithm>
#include <utility>
#include "Common/Logging/Log.h"
#include "Common/StringUtil.h"
#include "Common/VariantUtil.h"
#include "VideoCommon/Assets/CustomAssetLibrary.h"
namespace VideoCommon
{
bool ParseShaderProperties(const VideoCommon::CustomAssetLibrary::AssetID& asset_id,
const picojson::array& properties_data,
std::map<std::string, VideoCommon::ShaderProperty>* shader_properties)
template <typename ElementType, std::size_t ElementCount, typename PropertyType>
bool ParseNumeric(const CustomAssetLibrary::AssetID& asset_id, const picojson::value& json_value,
std::string_view code_name, PropertyType* value)
{
static_assert(ElementCount <= 4, "Numeric data expected to be four elements or less");
if constexpr (ElementCount == 1)
{
if (!json_value.is<double>())
{
ERROR_LOG_FMT(VIDEO,
"Asset id '{}' shader has attribute '{}' where "
"a double was expected but not provided.",
asset_id, code_name);
return false;
}
*value = static_cast<ElementType>(json_value.get<double>());
}
else
{
if (!json_value.is<picojson::array>())
{
ERROR_LOG_FMT(VIDEO,
"Asset id '{}' shader has attribute '{}' where "
"an array was expected but not provided.",
asset_id, code_name);
return false;
}
const auto json_data = json_value.get<picojson::array>();
if (json_data.size() != ElementCount)
{
ERROR_LOG_FMT(VIDEO,
"Asset id '{}' shader has attribute '{}' with incorrect number "
"of elements, expected {}",
asset_id, code_name, ElementCount);
return false;
}
if (!std::all_of(json_data.begin(), json_data.end(),
[](const picojson::value& v) { return v.is<double>(); }))
{
ERROR_LOG_FMT(VIDEO,
"Asset id '{}' shader has attribute '{}' where "
"all elements are not of type double.",
asset_id, code_name);
return false;
}
std::array<ElementType, ElementCount> data;
for (std::size_t i = 0; i < ElementCount; i++)
{
data[i] = static_cast<ElementType>(json_data[i].get<double>());
}
*value = std::move(data);
}
return true;
}
static bool ParseShaderValue(const CustomAssetLibrary::AssetID& asset_id,
const picojson::value& json_value, std::string_view code_name,
std::string_view type, ShaderProperty::Value* value)
{
if (type == "int")
{
return ParseNumeric<s32, 1>(asset_id, json_value, code_name, value);
}
else if (type == "int2")
{
return ParseNumeric<s32, 2>(asset_id, json_value, code_name, value);
}
else if (type == "int3")
{
return ParseNumeric<s32, 3>(asset_id, json_value, code_name, value);
}
else if (type == "int4")
{
return ParseNumeric<s32, 4>(asset_id, json_value, code_name, value);
}
else if (type == "float")
{
return ParseNumeric<float, 1>(asset_id, json_value, code_name, value);
}
else if (type == "float2")
{
return ParseNumeric<float, 2>(asset_id, json_value, code_name, value);
}
else if (type == "float3")
{
return ParseNumeric<float, 3>(asset_id, json_value, code_name, value);
}
else if (type == "float4")
{
return ParseNumeric<float, 4>(asset_id, json_value, code_name, value);
}
else if (type == "rgb")
{
ShaderProperty::RGB rgb;
if (!ParseNumeric<float, 3>(asset_id, json_value, code_name, &rgb.value))
return false;
*value = std::move(rgb);
return true;
}
else if (type == "rgba")
{
ShaderProperty::RGBA rgba;
if (!ParseNumeric<float, 4>(asset_id, json_value, code_name, &rgba.value))
return false;
*value = std::move(rgba);
return true;
}
else if (type == "bool")
{
if (json_value.is<bool>())
{
*value = json_value.get<bool>();
return true;
}
}
else if (type == "sampler2d")
{
if (json_value.is<std::string>())
{
ShaderProperty::Sampler2D sampler2d;
sampler2d.value = json_value.get<std::string>();
*value = std::move(sampler2d);
return true;
}
}
else if (type == "sampler2darray")
{
if (json_value.is<std::string>())
{
ShaderProperty::Sampler2DArray sampler2darray;
sampler2darray.value = json_value.get<std::string>();
*value = std::move(sampler2darray);
return true;
}
}
else if (type == "samplercube")
{
if (json_value.is<std::string>())
{
ShaderProperty::SamplerCube samplercube;
samplercube.value = json_value.get<std::string>();
*value = std::move(samplercube);
return true;
}
}
ERROR_LOG_FMT(VIDEO, "Asset '{}' failed to parse the json, value is not valid for type '{}'",
asset_id, type);
return false;
}
static bool
ParseShaderProperties(const VideoCommon::CustomAssetLibrary::AssetID& asset_id,
const picojson::array& properties_data,
std::map<std::string, VideoCommon::ShaderProperty>* shader_properties)
{
if (!shader_properties) [[unlikely]]
return false;
@ -42,33 +205,7 @@ bool ParseShaderProperties(const VideoCommon::CustomAssetLibrary::AssetID& asset
return false;
}
std::string type = type_iter->second.to_str();
std::transform(type.begin(), type.end(), type.begin(),
[](unsigned char c) { return std::tolower(c); });
if (type == "sampler2d")
{
property.m_type = ShaderProperty::Type::Type_Sampler2D;
}
else if (type == "samplercube")
{
property.m_type = ShaderProperty::Type::Type_SamplerCube;
}
else if (type == "samplerarrayshared_main")
{
property.m_type = ShaderProperty::Type::Type_SamplerArrayShared_Main;
}
else if (type == "samplerarrayshared_additional")
{
property.m_type = ShaderProperty::Type::Type_SamplerArrayShared_Additional;
}
else
{
ERROR_LOG_FMT(VIDEO,
"Asset '{}' failed to parse json, property entry 'description' is "
"an invalid option",
asset_id);
return false;
}
Common::ToLower(&type);
const auto description_iter = property_data_obj.find("description");
if (description_iter == property_data_obj.end())
@ -103,7 +240,18 @@ bool ParseShaderProperties(const VideoCommon::CustomAssetLibrary::AssetID& asset
asset_id);
return false;
}
shader_properties->try_emplace(code_name_iter->second.to_str(), std::move(property));
std::string code_name = code_name_iter->second.to_str();
const auto default_iter = property_data_obj.find("default");
if (default_iter != property_data_obj.end())
{
if (!ParseShaderValue(asset_id, default_iter->second, code_name, type, &property.m_default))
{
return false;
}
}
shader_properties->try_emplace(std::move(code_name), std::move(property));
}
return true;

View file

@ -3,8 +3,10 @@
#pragma once
#include <array>
#include <map>
#include <string>
#include <variant>
#include <picojson.h>
@ -14,22 +16,36 @@ namespace VideoCommon
{
struct ShaderProperty
{
// "SamplerShared" denotes that the sampler
// already exists outside of the shader source
// (ex: in the Dolphin defined pixel shader)
// "Main" is the first entry in a shared sampler array
// and "Additional" denotes a subsequent entry
// in the array
enum class Type
struct RGB
{
Type_Undefined,
Type_SamplerArrayShared_Main,
Type_SamplerArrayShared_Additional,
Type_Sampler2D,
Type_SamplerCube,
Type_Max = Type_SamplerCube
std::array<float, 3> value;
};
Type m_type;
struct RGBA
{
std::array<float, 4> value;
};
struct Sampler2D
{
CustomAssetLibrary::AssetID value;
};
struct Sampler2DArray
{
CustomAssetLibrary::AssetID value;
};
struct SamplerCube
{
CustomAssetLibrary::AssetID value;
};
using Value = std::variant<s32, std::array<s32, 2>, std::array<s32, 3>, std::array<s32, 4>, float,
std::array<float, 2>, std::array<float, 3>, std::array<float, 4>, bool,
RGB, RGBA, Sampler2D, Sampler2DArray, SamplerCube>;
Value m_default;
std::string m_description;
};
struct PixelShaderData

View file

@ -4,6 +4,7 @@
#include "VideoCommon/Assets/TextureAsset.h"
#include "Common/Logging/Log.h"
#include "Common/StringUtil.h"
#include "VideoCommon/BPMemory.h"
namespace VideoCommon
@ -31,8 +32,7 @@ bool ParseSampler(const VideoCommon::CustomAssetLibrary::AssetID& asset_id,
return false;
}
std::string sampler_state_mode = sampler_state_mode_iter->second.to_str();
std::transform(sampler_state_mode.begin(), sampler_state_mode.end(), sampler_state_mode.begin(),
[](unsigned char c) { return std::tolower(c); });
Common::ToLower(&sampler_state_mode);
if (sampler_state_mode == "clamp")
{
@ -71,8 +71,7 @@ bool ParseSampler(const VideoCommon::CustomAssetLibrary::AssetID& asset_id,
return false;
}
std::string sampler_state_filter = sampler_state_filter_iter->second.to_str();
std::transform(sampler_state_filter.begin(), sampler_state_filter.end(),
sampler_state_filter.begin(), [](unsigned char c) { return std::tolower(c); });
Common::ToLower(&sampler_state_filter);
if (sampler_state_filter == "linear")
{
sampler->tm0.min_filter = FilterMode::Linear;
@ -97,20 +96,6 @@ bool ParseSampler(const VideoCommon::CustomAssetLibrary::AssetID& asset_id,
return true;
}
} // namespace
CustomAssetLibrary::LoadInfo RawTextureAsset::LoadImpl(const CustomAssetLibrary::AssetID& asset_id)
{
auto potential_data = std::make_shared<CustomTextureData>();
const auto loaded_info = m_owning_library->LoadTexture(asset_id, potential_data.get());
if (loaded_info.m_bytes_loaded == 0)
return {};
{
std::lock_guard lk(m_data_lock);
m_loaded = true;
m_data = std::move(potential_data);
}
return loaded_info;
}
bool TextureData::FromJson(const CustomAssetLibrary::AssetID& asset_id,
const picojson::object& json, TextureData* data)
{
@ -130,8 +115,7 @@ bool TextureData::FromJson(const CustomAssetLibrary::AssetID& asset_id,
return false;
}
std::string type = type_iter->second.to_str();
std::transform(type.begin(), type.end(), type.begin(),
[](unsigned char c) { return std::tolower(c); });
Common::ToLower(&type);
if (type == "texture2d")
{
@ -160,7 +144,7 @@ bool TextureData::FromJson(const CustomAssetLibrary::AssetID& asset_id,
CustomAssetLibrary::LoadInfo GameTextureAsset::LoadImpl(const CustomAssetLibrary::AssetID& asset_id)
{
auto potential_data = std::make_shared<CustomTextureData>();
auto potential_data = std::make_shared<TextureData>();
const auto loaded_info = m_owning_library->LoadGameTexture(asset_id, potential_data.get());
if (loaded_info.m_bytes_loaded == 0)
return {};
@ -184,7 +168,7 @@ bool GameTextureAsset::Validate(u32 native_width, u32 native_height) const
return false;
}
if (m_data->m_slices.empty())
if (m_data->m_texture.m_slices.empty())
{
ERROR_LOG_FMT(VIDEO,
"Game texture can't be validated for asset '{}' because no data was available.",
@ -192,7 +176,7 @@ bool GameTextureAsset::Validate(u32 native_width, u32 native_height) const
return false;
}
if (m_data->m_slices.size() > 1)
if (m_data->m_texture.m_slices.size() > 1)
{
ERROR_LOG_FMT(
VIDEO,
@ -201,7 +185,7 @@ bool GameTextureAsset::Validate(u32 native_width, u32 native_height) const
return false;
}
const auto& slice = m_data->m_slices[0];
const auto& slice = m_data->m_texture.m_slices[0];
if (slice.m_levels.empty())
{
ERROR_LOG_FMT(

View file

@ -3,23 +3,16 @@
#pragma once
#include <fmt/format.h>
#include <picojson.h>
#include "Common/EnumFormatter.h"
#include "VideoCommon/Assets/CustomAsset.h"
#include "VideoCommon/Assets/CustomTextureData.h"
#include "VideoCommon/RenderState.h"
namespace VideoCommon
{
class RawTextureAsset final : public CustomLoadableAsset<CustomTextureData>
{
public:
using CustomLoadableAsset::CustomLoadableAsset;
private:
CustomAssetLibrary::LoadInfo LoadImpl(const CustomAssetLibrary::AssetID& asset_id) override;
};
struct TextureData
{
static bool FromJson(const CustomAssetLibrary::AssetID& asset_id, const picojson::object& json,
@ -32,11 +25,11 @@ struct TextureData
Type_Max = Type_TextureCube
};
Type m_type;
CustomTextureData m_data;
CustomTextureData m_texture;
SamplerState m_sampler;
};
class GameTextureAsset final : public CustomLoadableAsset<CustomTextureData>
class GameTextureAsset final : public CustomLoadableAsset<TextureData>
{
public:
using CustomLoadableAsset::CustomLoadableAsset;
@ -49,3 +42,10 @@ private:
CustomAssetLibrary::LoadInfo LoadImpl(const CustomAssetLibrary::AssetID& asset_id) override;
};
} // namespace VideoCommon
template <>
struct fmt::formatter<VideoCommon::TextureData::Type>
: EnumFormatter<VideoCommon::TextureData::Type::Type_Max>
{
constexpr formatter() : EnumFormatter({"Undefined", "Texture2D", "TextureCube"}) {}
};

View file

@ -96,7 +96,7 @@ void AsyncRequests::PushEvent(const AsyncRequests::Event& event, bool blocking)
m_queue.push(event);
auto& system = Core::System::GetInstance();
system.GetFifo().RunGpu(system);
system.GetFifo().RunGpu();
if (blocking)
{
m_cond.wait(lock, [this] { return m_queue.empty(); });

View file

@ -11,8 +11,8 @@ BPMemory bpmem;
bool BlendMode::UseLogicOp() const
{
// Logicop bit has lowest priority.
if (subtract || blendenable || !logicopenable)
// Blending overrides the logicop bit.
if (blendenable || !logicopenable)
return false;
// Fast path for Kirby's Return to Dreamland, they use it with dstAlpha.

View file

@ -56,7 +56,7 @@ enum
BPMEM_EFB_TL = 0x49,
BPMEM_EFB_WH = 0x4A,
BPMEM_EFB_ADDR = 0x4B,
BPMEM_MIPMAP_STRIDE = 0x4D,
BPMEM_EFB_STRIDE = 0x4D,
BPMEM_COPYYSCALE = 0x4E,
BPMEM_CLEAR_AR = 0x4F,
BPMEM_CLEAR_GB = 0x50,
@ -1037,12 +1037,12 @@ struct fmt::formatter<TexImage1>
auto format(const TexImage1& teximg, FormatContext& ctx) const
{
return fmt::format_to(ctx.out(),
"Even TMEM Offset: {:x}\n"
"Even TMEM Line: 0x{:04x} (byte 0x{:05x})\n"
"Even TMEM Width: {}\n"
"Even TMEM Height: {}\n"
"Cache is manually managed: {}",
teximg.tmem_even, teximg.cache_width, teximg.cache_height,
teximg.cache_manually_managed ? "Yes" : "No");
teximg.tmem_even, teximg.tmem_even * 32, teximg.cache_width,
teximg.cache_height, teximg.cache_manually_managed ? "Yes" : "No");
}
};
@ -1061,10 +1061,11 @@ struct fmt::formatter<TexImage2>
auto format(const TexImage2& teximg, FormatContext& ctx) const
{
return fmt::format_to(ctx.out(),
"Odd TMEM Offset: {:x}\n"
"Odd TMEM Line: 0x{:04x} (byte 0x{:05x})\n"
"Odd TMEM Width: {}\n"
"Odd TMEM Height: {}",
teximg.tmem_odd, teximg.cache_width, teximg.cache_height);
teximg.tmem_odd, teximg.tmem_odd * 32, teximg.cache_width,
teximg.cache_height);
}
};
@ -1080,7 +1081,7 @@ struct fmt::formatter<TexImage3>
template <typename FormatContext>
auto format(const TexImage3& teximg, FormatContext& ctx) const
{
return fmt::format_to(ctx.out(), "Source address (32 byte aligned): 0x{:06X}",
return fmt::format_to(ctx.out(), "Source address (32 byte aligned): 0x{:06x}",
teximg.image_base << 5);
}
};
@ -1098,7 +1099,7 @@ struct fmt::formatter<TexTLUT>
template <typename FormatContext>
auto format(const TexTLUT& tlut, FormatContext& ctx) const
{
return fmt::format_to(ctx.out(), "Address: {:08x}\nFormat: {}", tlut.tmem_offset << 9,
return fmt::format_to(ctx.out(), "Tmem address: 0x{:05x}\nFormat: {}", tlut.tmem_offset << 9,
tlut.tlut_format);
}
};
@ -1108,6 +1109,16 @@ union ZTex1
BitField<0, 24, u32> bias;
u32 hex;
};
template <>
struct fmt::formatter<ZTex1>
{
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
template <typename FormatContext>
auto format(const ZTex1& ztex1, FormatContext& ctx) const
{
return fmt::format_to(ctx.out(), "Bias: 0x{:06x}", ztex1.bias);
}
};
union ZTex2
{
@ -2218,7 +2229,7 @@ union CopyFilterCoefficients
union BPU_PreloadTileInfo
{
BitField<0, 15, u32> count;
BitField<15, 2, u32> type;
BitField<15, 2, u32> type; // TODO: enum for this?
u32 hex;
};
template <>
@ -2228,7 +2239,33 @@ struct fmt::formatter<BPU_PreloadTileInfo>
template <typename FormatContext>
auto format(const BPU_PreloadTileInfo& info, FormatContext& ctx) const
{
return fmt::format_to(ctx.out(), "Type: {}\nCount: {}", info.type, info.count);
if (info.count == 0 && info.type == 0)
{
return fmt::format_to(ctx.out(), "GX_TexModeSync (type and count are both 0)");
}
else
{
return fmt::format_to(ctx.out(), "Type: {}\nCount: 0x{:x} lines (0x{:x} bytes)", info.type,
info.count, info.count * 32);
}
}
};
union BPU_LoadTlutInfo
{
BitField<0, 10, u32> tmem_addr;
BitField<10, 11, u32> tmem_line_count;
u32 hex;
};
template <>
struct fmt::formatter<BPU_LoadTlutInfo>
{
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
template <typename FormatContext>
auto format(const BPU_LoadTlutInfo& info, FormatContext& ctx) const
{
return fmt::format_to(ctx.out(), "Tmem address: 0x{:05x}\nCount: 0x{:x} lines (0x{:x} bytes)",
info.tmem_addr << 9, info.tmem_line_count, info.tmem_line_count * 32);
}
};
@ -2239,7 +2276,7 @@ struct BPS_TmemConfig
u32 preload_tmem_odd;
BPU_PreloadTileInfo preload_tile_info;
u32 tlut_src;
u32 tlut_dest;
BPU_LoadTlutInfo tlut_dest;
u32 texinvalidate;
};
@ -2426,35 +2463,32 @@ struct BPMemory
// the 3 offset matrices can either be indirect type, S-type, or T-type
// 6bit scale factor s is distributed across IND_MTXA/B/C.
// before using matrices scale by 2^-(s-17)
IND_MTX indmtx[3]; // 0x06-0x0e: GXSetIndTexMtx, 2x3 matrices
IND_IMASK imask; // 0x0f
TevStageIndirect tevind[16]; // 0x10-0x1f: GXSetTevIndirect
ScissorPos scissorTL; // 0x20
ScissorPos scissorBR; // 0x21
LPSize lineptwidth; // 0x22
u32 sucounter; // 0x23
u32 rascounter; // 0x24
TEXSCALE texscale[2]; // 0x25,0x26: GXSetIndTexCoordScale
RAS1_IREF tevindref; // 0x27: GXSetIndTexOrder
TwoTevStageOrders tevorders[8]; // 0x28-0x2f
TCoordInfo texcoords[8]; // 0x30-0x4f: s,t,s,t,s,t,s,t...
ZMode zmode; // 0x40
BlendMode blendmode; // 0x41
ConstantAlpha dstalpha; // 0x42
PEControl zcontrol; // 0x43: GXSetZCompLoc, GXPixModeSync
FieldMask fieldmask; // 0x44
u32 drawdone; // 0x45: bit1=1 if end of list
u32 unknown5; // 0x46: clock?
u32 petoken; // 0x47
u32 petokenint; // 0x48
X10Y10 copyTexSrcXY; // 0x49
X10Y10 copyTexSrcWH; // 0x4a
u32 copyTexDest; // 0x4b: CopyAddress (GXDispCopy and GXTexCopy use it)
u32 unknown6; // 0x4c
// usually set to 4 when dest is single channel, 8 when dest is 2 channel, 16 when dest is RGBA
// also, doubles whenever mipmap box filter option is set (excent on RGBA). Probably to do
// with number of bytes to look at when smoothing
u32 copyMipMapStrideChannels; // 0x4d
IND_MTX indmtx[3]; // 0x06-0x0e: GXSetIndTexMtx, 2x3 matrices
IND_IMASK imask; // 0x0f
TevStageIndirect tevind[16]; // 0x10-0x1f: GXSetTevIndirect
ScissorPos scissorTL; // 0x20
ScissorPos scissorBR; // 0x21
LPSize lineptwidth; // 0x22
u32 sucounter; // 0x23
u32 rascounter; // 0x24
TEXSCALE texscale[2]; // 0x25,0x26: GXSetIndTexCoordScale
RAS1_IREF tevindref; // 0x27: GXSetIndTexOrder
TwoTevStageOrders tevorders[8]; // 0x28-0x2f
TCoordInfo texcoords[8]; // 0x30-0x3f: s,t,s,t,s,t,s,t...
ZMode zmode; // 0x40
BlendMode blendmode; // 0x41
ConstantAlpha dstalpha; // 0x42
PEControl zcontrol; // 0x43: GXSetZCompLoc, GXPixModeSync
FieldMask fieldmask; // 0x44
u32 drawdone; // 0x45: bit1=1 if end of list
u32 unknown5; // 0x46: clock?
u32 petoken; // 0x47
u32 petokenint; // 0x48
X10Y10 copyTexSrcXY; // 0x49
X10Y10 copyTexSrcWH; // 0x4a
u32 copyTexDest; // 0x4b: CopyAddress (GXDispCopy and GXTexCopy use it)
u32 unknown6; // 0x4c
u32 copyDestStride; // 0x4d
u32 dispcopyyscale; // 0x4e
u32 clearcolorAR; // 0x4f
u32 clearcolorGB; // 0x50

View file

@ -38,11 +38,11 @@
#include "VideoCommon/TMEM.h"
#include "VideoCommon/TextureCacheBase.h"
#include "VideoCommon/TextureDecoder.h"
#include "VideoCommon/VertexShaderManager.h"
#include "VideoCommon/VideoBackendBase.h"
#include "VideoCommon/VideoCommon.h"
#include "VideoCommon/VideoConfig.h"
#include "VideoCommon/VideoEvents.h"
#include "VideoCommon/XFStateManager.h"
using namespace BPFunctions;
@ -55,8 +55,7 @@ void BPInit()
bpmem.bpMask = 0xFFFFFF;
}
static void BPWritten(PixelShaderManager& pixel_shader_manager,
VertexShaderManager& vertex_shader_manager,
static void BPWritten(PixelShaderManager& pixel_shader_manager, XFStateManager& xf_state_manager,
GeometryShaderManager& geometry_shader_manager, const BPCmd& bp,
int cycles_into_future)
{
@ -139,7 +138,7 @@ static void BPWritten(PixelShaderManager& pixel_shader_manager,
case BPMEM_SCISSORTL: // Scissor Rectable Top, Left
case BPMEM_SCISSORBR: // Scissor Rectable Bottom, Right
case BPMEM_SCISSOROFFSET: // Scissor Offset
vertex_shader_manager.SetViewportChanged();
xf_state_manager.SetViewportChanged();
geometry_shader_manager.SetViewportChanged();
return;
case BPMEM_LINEPTWIDTH: // Line Width
@ -191,7 +190,7 @@ static void BPWritten(PixelShaderManager& pixel_shader_manager,
g_framebuffer_manager->RefreshPeekCache();
auto& system = Core::System::GetInstance();
if (!system.GetFifo().UseDeterministicGPUThread())
system.GetPixelEngine().SetFinish(system, cycles_into_future); // may generate interrupt
system.GetPixelEngine().SetFinish(cycles_into_future); // may generate interrupt
DEBUG_LOG_FMT(VIDEO, "GXSetDrawDone SetPEFinish (value: {:#04X})", bp.newvalue & 0xFFFF);
return;
}
@ -211,7 +210,7 @@ static void BPWritten(PixelShaderManager& pixel_shader_manager,
auto& system = Core::System::GetInstance();
if (!system.GetFifo().UseDeterministicGPUThread())
{
system.GetPixelEngine().SetToken(system, static_cast<u16>(bp.newvalue & 0xFFFF), false,
system.GetPixelEngine().SetToken(static_cast<u16>(bp.newvalue & 0xFFFF), false,
cycles_into_future);
}
DEBUG_LOG_FMT(VIDEO, "SetPEToken {:#06X}", bp.newvalue & 0xFFFF);
@ -227,7 +226,7 @@ static void BPWritten(PixelShaderManager& pixel_shader_manager,
auto& system = Core::System::GetInstance();
if (!system.GetFifo().UseDeterministicGPUThread())
{
system.GetPixelEngine().SetToken(system, static_cast<u16>(bp.newvalue & 0xFFFF), true,
system.GetPixelEngine().SetToken(static_cast<u16>(bp.newvalue & 0xFFFF), true,
cycles_into_future);
}
DEBUG_LOG_FMT(VIDEO, "SetPEToken + INT {:#06X}", bp.newvalue & 0xFFFF);
@ -246,7 +245,7 @@ static void BPWritten(PixelShaderManager& pixel_shader_manager,
// this function
u32 destAddr = bpmem.copyTexDest << 5;
u32 destStride = bpmem.copyMipMapStrideChannels << 5;
u32 destStride = bpmem.copyDestStride << 5;
MathUtil::Rectangle<s32> srcRect;
srcRect.left = bpmem.copyTexSrcXY.x;
@ -380,24 +379,32 @@ static void BPWritten(PixelShaderManager& pixel_shader_manager,
return;
}
case BPMEM_LOADTLUT0: // This one updates bpmem.tlutXferSrc, no need to do anything here.
case BPMEM_LOADTLUT0: // This updates bpmem.tmem_config.tlut_src, no need to do anything here.
return;
case BPMEM_LOADTLUT1: // Load a Texture Look Up Table
{
u32 tlutTMemAddr = (bp.newvalue & 0x3FF) << 9;
u32 tlutXferCount = (bp.newvalue & 0x1FFC00) >> 5;
u32 tmem_addr = bpmem.tmem_config.tlut_dest.tmem_addr << 9;
u32 tmem_transfer_count = bpmem.tmem_config.tlut_dest.tmem_line_count * TMEM_LINE_SIZE;
u32 addr = bpmem.tmem_config.tlut_src << 5;
// The GameCube ignores the upper bits of this address. Some games (WW, MKDD) set them.
if (!SConfig::GetInstance().bWii)
addr = addr & 0x01FFFFFF;
// The copy below will always be in bounds as tmem is bigger than the maximum address a TLUT can
// be loaded to.
static constexpr u32 MAX_LOADABLE_TMEM_ADDR =
(1 << bpmem.tmem_config.tlut_dest.tmem_addr.NumBits()) << 9;
static constexpr u32 MAX_TMEM_LINE_COUNT =
(1 << bpmem.tmem_config.tlut_dest.tmem_line_count.NumBits()) * TMEM_LINE_SIZE;
static_assert(MAX_LOADABLE_TMEM_ADDR + MAX_TMEM_LINE_COUNT < TMEM_SIZE);
auto& system = Core::System::GetInstance();
auto& memory = system.GetMemory();
memory.CopyFromEmu(texMem + tlutTMemAddr, addr, tlutXferCount);
memory.CopyFromEmu(texMem + tmem_addr, addr, tmem_transfer_count);
if (OpcodeDecoder::g_record_fifo_data)
FifoRecorder::GetInstance().UseMemory(addr, tlutXferCount, MemoryUpdate::Type::TMEM);
FifoRecorder::GetInstance().UseMemory(addr, tmem_transfer_count, MemoryUpdate::Type::TMEM);
TMEM::InvalidateAll();
@ -515,8 +522,9 @@ static void BPWritten(PixelShaderManager& pixel_shader_manager,
pixel_shader_manager.SetZModeControl();
return;
case BPMEM_MIPMAP_STRIDE: // MipMap Stride Channel
case BPMEM_COPYYSCALE: // Display Copy Y Scale
case BPMEM_EFB_STRIDE: // Display Copy Stride
case BPMEM_COPYYSCALE: // Display Copy Y Scale
return;
/* 24 RID
* 21 BC3 - Ind. Tex Stage 3 NTexCoord
@ -781,7 +789,7 @@ void LoadBPReg(u8 reg, u32 value, int cycles_into_future)
if (reg != BPMEM_BP_MASK)
bpmem.bpMask = 0xFFFFFF;
BPWritten(system.GetPixelShaderManager(), system.GetVertexShaderManager(),
BPWritten(system.GetPixelShaderManager(), system.GetXFStateManager(),
system.GetGeometryShaderManager(), bp, cycles_into_future);
}
@ -795,13 +803,13 @@ void LoadBPRegPreprocess(u8 reg, u32 value, int cycles_into_future)
{
case BPMEM_SETDRAWDONE:
if ((newval & 0xff) == 0x02)
system.GetPixelEngine().SetFinish(system, cycles_into_future);
system.GetPixelEngine().SetFinish(cycles_into_future);
break;
case BPMEM_PE_TOKEN_ID:
system.GetPixelEngine().SetToken(system, newval & 0xffff, false, cycles_into_future);
system.GetPixelEngine().SetToken(newval & 0xffff, false, cycles_into_future);
break;
case BPMEM_PE_TOKEN_INT_ID: // Pixel Engine Interrupt Token ID
system.GetPixelEngine().SetToken(system, newval & 0xffff, true, cycles_into_future);
system.GetPixelEngine().SetToken(newval & 0xffff, true, cycles_into_future);
break;
}
}
@ -982,9 +990,10 @@ std::pair<std::string, std::string> GetBPRegInfo(u8 cmd, u32 cmddata)
RegName(BPMEM_EFB_ADDR),
fmt::format("EFB Target address (32 byte aligned): 0x{:06X}", cmddata << 5));
case BPMEM_MIPMAP_STRIDE: // 0x4D
return DescriptionlessReg(BPMEM_MIPMAP_STRIDE);
// TODO: Description
case BPMEM_EFB_STRIDE: // 0x4D
return std::make_pair(
RegName(BPMEM_EFB_STRIDE),
fmt::format("EFB destination stride (32 byte aligned): 0x{:06X}", cmddata << 5));
case BPMEM_COPYYSCALE: // 0x4E
return std::make_pair(
@ -1030,12 +1039,14 @@ std::pair<std::string, std::string> GetBPRegInfo(u8 cmd, u32 cmddata)
}
case BPMEM_CLEARBBOX1: // 0x55
return DescriptionlessReg(BPMEM_CLEARBBOX1);
// TODO: Description
return std::make_pair(RegName(BPMEM_CLEARBBOX1),
fmt::format("Bounding Box index 0: {}\nBounding Box index 1: {}",
cmddata & 0x3ff, (cmddata >> 10) & 0x3ff));
case BPMEM_CLEARBBOX2: // 0x56
return DescriptionlessReg(BPMEM_CLEARBBOX2);
// TODO: Description
return std::make_pair(RegName(BPMEM_CLEARBBOX2),
fmt::format("Bounding Box index 2: {}\nBounding Box index 3: {}",
cmddata & 0x3ff, (cmddata >> 10) & 0x3ff));
case BPMEM_CLEAR_PIXEL_PERF: // 0x57
return DescriptionlessReg(BPMEM_CLEAR_PIXEL_PERF);
@ -1050,28 +1061,33 @@ std::pair<std::string, std::string> GetBPRegInfo(u8 cmd, u32 cmddata)
fmt::to_string(ScissorOffset{.hex = cmddata}));
case BPMEM_PRELOAD_ADDR: // 0x60
return DescriptionlessReg(BPMEM_PRELOAD_ADDR);
// TODO: Description
return std::make_pair(
RegName(BPMEM_PRELOAD_ADDR),
fmt::format("Tmem preload address (32 byte aligned, in main memory): 0x{:06x}",
cmddata << 5));
case BPMEM_PRELOAD_TMEMEVEN: // 0x61
return DescriptionlessReg(BPMEM_PRELOAD_TMEMEVEN);
// TODO: Description
return std::make_pair(RegName(BPMEM_PRELOAD_TMEMEVEN),
fmt::format("Tmem preload even line: 0x{:04x} (byte 0x{:05x})", cmddata,
cmddata * TMEM_LINE_SIZE));
case BPMEM_PRELOAD_TMEMODD: // 0x62
return DescriptionlessReg(BPMEM_PRELOAD_TMEMODD);
// TODO: Description
return std::make_pair(RegName(BPMEM_PRELOAD_TMEMODD),
fmt::format("Tmem preload odd line: 0x{:04x} (byte 0x{:05x})", cmddata,
cmddata * TMEM_LINE_SIZE));
case BPMEM_PRELOAD_MODE: // 0x63
return std::make_pair(RegName(BPMEM_PRELOAD_MODE),
fmt::to_string(BPU_PreloadTileInfo{.hex = cmddata}));
case BPMEM_LOADTLUT0: // 0x64
return DescriptionlessReg(BPMEM_LOADTLUT0);
// TODO: Description
return std::make_pair(
RegName(BPMEM_LOADTLUT0),
fmt::format("TLUT load address (32 byte aligned, in main memory): 0x{:06x}", cmddata << 5));
case BPMEM_LOADTLUT1: // 0x65
return DescriptionlessReg(BPMEM_LOADTLUT1);
// TODO: Description
return std::make_pair(RegName(BPMEM_LOADTLUT1),
fmt::to_string(BPU_LoadTlutInfo{.hex = cmddata}));
case BPMEM_TEXINVALIDATE: // 0x66
return DescriptionlessReg(BPMEM_TEXINVALIDATE);
@ -1269,12 +1285,11 @@ std::pair<std::string, std::string> GetBPRegInfo(u8 cmd, u32 cmddata)
return std::make_pair(RegName(BPMEM_FOGPARAM0), fmt::to_string(FogParam0{.hex = cmddata}));
case BPMEM_FOGBMAGNITUDE: // 0xEF
return DescriptionlessReg(BPMEM_FOGBMAGNITUDE);
// TODO: Description
return std::make_pair(RegName(BPMEM_FOGBMAGNITUDE), fmt::format("B magnitude: {}", cmddata));
case BPMEM_FOGBEXPONENT: // 0xF0
return DescriptionlessReg(BPMEM_FOGBEXPONENT);
// TODO: Description
return std::make_pair(RegName(BPMEM_FOGBEXPONENT),
fmt::format("B shift: 1>>{} (1/{})", cmddata, 1 << cmddata));
case BPMEM_FOGPARAM3: // 0xF1
return std::make_pair(RegName(BPMEM_FOGPARAM3), fmt::to_string(FogParam3{.hex = cmddata}));
@ -1287,8 +1302,7 @@ std::pair<std::string, std::string> GetBPRegInfo(u8 cmd, u32 cmddata)
return std::make_pair(RegName(BPMEM_ALPHACOMPARE), fmt::to_string(AlphaTest{.hex = cmddata}));
case BPMEM_BIAS: // 0xF4
return DescriptionlessReg(BPMEM_BIAS);
// TODO: Description
return std::make_pair(RegName(BPMEM_BIAS), fmt::to_string(ZTex1{.hex = cmddata}));
case BPMEM_ZTEX2: // 0xF5
return std::make_pair(RegName(BPMEM_ZTEX2), fmt::to_string(ZTex2{.hex = cmddata}));

View file

@ -51,7 +51,7 @@ void BoundingBox::Flush()
for (u32 i = start; i < end; ++i)
m_dirty[i] = false;
Write(start, std::vector<BBoxType>(m_values.begin() + start, m_values.begin() + end));
Write(start, std::span(m_values.begin() + start, m_values.begin() + end));
}
}

View file

@ -5,6 +5,7 @@
#include <array>
#include <memory>
#include <span>
#include <vector>
#include "Common/CommonTypes.h"
@ -38,8 +39,7 @@ public:
protected:
virtual std::vector<BBoxType> Read(u32 index, u32 length) = 0;
// TODO: This can likely use std::span once we're on C++20
virtual void Write(u32 index, const std::vector<BBoxType>& values) = 0;
virtual void Write(u32 index, std::span<const BBoxType> values) = 0;
private:
void Readback();

View file

@ -155,6 +155,8 @@ add_library(videocommon
TextureDecoder_Util.h
TextureInfo.cpp
TextureInfo.h
TextureUtils.cpp
TextureUtils.h
TMEM.cpp
TMEM.h
UberShaderCommon.cpp
@ -196,6 +198,8 @@ add_library(videocommon
Widescreen.h
XFMemory.cpp
XFMemory.h
XFStateManager.cpp
XFStateManager.h
XFStructs.cpp
XFStructs.h
)
@ -212,7 +216,7 @@ PRIVATE
glslang
)
if(_M_X86)
if(_M_X86_64)
target_sources(videocommon PRIVATE
TextureDecoder_x64.cpp
VertexLoaderX64.cpp

View file

@ -575,6 +575,102 @@ struct VAT
return 0;
}
}
void SetTexElements(size_t idx, TexComponentCount value)
{
switch (idx)
{
case 0:
g0.Tex0CoordElements = value;
return;
case 1:
g1.Tex1CoordElements = value;
return;
case 2:
g1.Tex2CoordElements = value;
return;
case 3:
g1.Tex3CoordElements = value;
return;
case 4:
g1.Tex4CoordElements = value;
return;
case 5:
g2.Tex5CoordElements = value;
return;
case 6:
g2.Tex6CoordElements = value;
return;
case 7:
g2.Tex7CoordElements = value;
return;
default:
PanicAlertFmt("Invalid tex coord index {}", idx);
}
}
void SetTexFormat(size_t idx, ComponentFormat value)
{
switch (idx)
{
case 0:
g0.Tex0CoordFormat = value;
return;
case 1:
g1.Tex1CoordFormat = value;
return;
case 2:
g1.Tex2CoordFormat = value;
return;
case 3:
g1.Tex3CoordFormat = value;
return;
case 4:
g1.Tex4CoordFormat = value;
return;
case 5:
g2.Tex5CoordFormat = value;
return;
case 6:
g2.Tex6CoordFormat = value;
return;
case 7:
g2.Tex7CoordFormat = value;
return;
default:
PanicAlertFmt("Invalid tex coord index {}", idx);
}
}
void SetTexFrac(size_t idx, u8 value)
{
switch (idx)
{
case 0:
g0.Tex0Frac = value;
return;
case 1:
g1.Tex1Frac = value;
return;
case 2:
g1.Tex2Frac = value;
return;
case 3:
g1.Tex3Frac = value;
return;
case 4:
g2.Tex4Frac = value;
return;
case 5:
g2.Tex5Frac = value;
return;
case 6:
g2.Tex6Frac = value;
return;
case 7:
g2.Tex7Frac = value;
return;
default:
PanicAlertFmt("Invalid tex coord index {}", idx);
}
}
};
template <>
struct fmt::formatter<VAT>

View file

@ -14,6 +14,7 @@
#include "VideoCommon/VertexShaderManager.h"
#include "VideoCommon/VideoConfig.h"
#include "VideoCommon/XFMemory.h"
#include "VideoCommon/XFStateManager.h"
// We really want things like c.w * a.x - a.w * c.x to stay symmetric, so they cancel to zero on
// degenerate triangles. Make sure the compiler doesn't optimize in fmas where not requested.
@ -153,7 +154,8 @@ bool CPUCull::AreAllVerticesCulled(VertexLoaderBase* loader, OpcodeDecoder::Prim
}
// transform functions need the projection matrix to tranform to clip space
Core::System::GetInstance().GetVertexShaderManager().SetProjectionMatrix();
auto& system = Core::System::GetInstance();
system.GetVertexShaderManager().SetProjectionMatrix(system.GetXFStateManager());
static constexpr Common::EnumMap<CullMode, CullMode::All> cullmode_invert = {
CullMode::None, CullMode::Front, CullMode::Back, CullMode::All};

View file

@ -24,14 +24,14 @@
namespace CommandProcessor
{
static bool IsOnThread(Core::System& system)
static bool IsOnThread(const Core::System& system)
{
return system.IsDualCoreMode();
}
static void UpdateInterrupts_Wrapper(Core::System& system, u64 userdata, s64 cyclesLate)
{
system.GetCommandProcessor().UpdateInterrupts(system, userdata);
system.GetCommandProcessor().UpdateInterrupts(userdata);
}
void SCPFifoStruct::Init()
@ -105,7 +105,11 @@ static inline void WriteHigh(std::atomic<u32>& reg, u16 highbits)
std::memory_order_relaxed);
}
void CommandProcessorManager::Init(Core::System& system)
CommandProcessorManager::CommandProcessorManager(Core::System& system) : m_system{system}
{
}
void CommandProcessorManager::Init()
{
m_cp_status_reg.Hex = 0;
m_cp_status_reg.CommandIdle = 1;
@ -130,7 +134,7 @@ void CommandProcessorManager::Init(Core::System& system)
m_interrupt_waiting.Clear();
m_event_type_update_interrupts =
system.GetCoreTiming().RegisterEvent("CPInterrupt", UpdateInterrupts_Wrapper);
m_system.GetCoreTiming().RegisterEvent("CPInterrupt", UpdateInterrupts_Wrapper);
}
u32 GetPhysicalAddressMask()
@ -140,15 +144,13 @@ u32 GetPhysicalAddressMask()
return SConfig::GetInstance().bWii ? 0x1fffffff : 0x03ffffff;
}
void CommandProcessorManager::RegisterMMIO(Core::System& system, MMIO::Mapping* mmio, u32 base)
void CommandProcessorManager::RegisterMMIO(MMIO::Mapping* mmio, u32 base)
{
constexpr u16 WMASK_NONE = 0x0000;
constexpr u16 WMASK_ALL = 0xffff;
constexpr u16 WMASK_LO_ALIGN_32BIT = 0xffe0;
const u16 WMASK_HI_RESTRICT = GetPhysicalAddressMask() >> 16;
auto& fifo = m_fifo;
struct
{
u32 addr;
@ -166,25 +168,27 @@ void CommandProcessorManager::RegisterMMIO(Core::System& system, MMIO::Mapping*
{FIFO_BOUNDING_BOX_RIGHT, &m_bbox_right, true, WMASK_NONE},
{FIFO_BOUNDING_BOX_TOP, &m_bbox_top, true, WMASK_NONE},
{FIFO_BOUNDING_BOX_BOTTOM, &m_bbox_bottom, true, WMASK_NONE},
{FIFO_BASE_LO, MMIO::Utils::LowPart(&fifo.CPBase), false, WMASK_LO_ALIGN_32BIT},
{FIFO_BASE_HI, MMIO::Utils::HighPart(&fifo.CPBase), false, WMASK_HI_RESTRICT},
{FIFO_END_LO, MMIO::Utils::LowPart(&fifo.CPEnd), false, WMASK_LO_ALIGN_32BIT},
{FIFO_END_HI, MMIO::Utils::HighPart(&fifo.CPEnd), false, WMASK_HI_RESTRICT},
{FIFO_HI_WATERMARK_LO, MMIO::Utils::LowPart(&fifo.CPHiWatermark), false,
{FIFO_BASE_LO, MMIO::Utils::LowPart(&m_fifo.CPBase), false, WMASK_LO_ALIGN_32BIT},
{FIFO_BASE_HI, MMIO::Utils::HighPart(&m_fifo.CPBase), false, WMASK_HI_RESTRICT},
{FIFO_END_LO, MMIO::Utils::LowPart(&m_fifo.CPEnd), false, WMASK_LO_ALIGN_32BIT},
{FIFO_END_HI, MMIO::Utils::HighPart(&m_fifo.CPEnd), false, WMASK_HI_RESTRICT},
{FIFO_HI_WATERMARK_LO, MMIO::Utils::LowPart(&m_fifo.CPHiWatermark), false,
WMASK_LO_ALIGN_32BIT},
{FIFO_HI_WATERMARK_HI, MMIO::Utils::HighPart(&fifo.CPHiWatermark), false, WMASK_HI_RESTRICT},
{FIFO_LO_WATERMARK_LO, MMIO::Utils::LowPart(&fifo.CPLoWatermark), false,
{FIFO_HI_WATERMARK_HI, MMIO::Utils::HighPart(&m_fifo.CPHiWatermark), false,
WMASK_HI_RESTRICT},
{FIFO_LO_WATERMARK_LO, MMIO::Utils::LowPart(&m_fifo.CPLoWatermark), false,
WMASK_LO_ALIGN_32BIT},
{FIFO_LO_WATERMARK_HI, MMIO::Utils::HighPart(&fifo.CPLoWatermark), false, WMASK_HI_RESTRICT},
{FIFO_LO_WATERMARK_HI, MMIO::Utils::HighPart(&m_fifo.CPLoWatermark), false,
WMASK_HI_RESTRICT},
// FIFO_RW_DISTANCE has some complex read code different for
// single/dual core.
{FIFO_WRITE_POINTER_LO, MMIO::Utils::LowPart(&fifo.CPWritePointer), false,
{FIFO_WRITE_POINTER_LO, MMIO::Utils::LowPart(&m_fifo.CPWritePointer), false,
WMASK_LO_ALIGN_32BIT},
{FIFO_WRITE_POINTER_HI, MMIO::Utils::HighPart(&fifo.CPWritePointer), false,
{FIFO_WRITE_POINTER_HI, MMIO::Utils::HighPart(&m_fifo.CPWritePointer), false,
WMASK_HI_RESTRICT},
// FIFO_READ_POINTER has different code for single/dual core.
{FIFO_BP_LO, MMIO::Utils::LowPart(&fifo.CPBreakpoint), false, WMASK_LO_ALIGN_32BIT},
{FIFO_BP_HI, MMIO::Utils::HighPart(&fifo.CPBreakpoint), false, WMASK_HI_RESTRICT},
{FIFO_BP_LO, MMIO::Utils::LowPart(&m_fifo.CPBreakpoint), false, WMASK_LO_ALIGN_32BIT},
{FIFO_BP_HI, MMIO::Utils::HighPart(&m_fifo.CPBreakpoint), false, WMASK_HI_RESTRICT},
};
for (auto& mapped_var : directly_mapped_vars)
@ -224,8 +228,8 @@ void CommandProcessorManager::RegisterMMIO(Core::System& system, MMIO::Mapping*
mmio->Register(base | STATUS_REGISTER, MMIO::ComplexRead<u16>([](Core::System& system_, u32) {
auto& cp = system_.GetCommandProcessor();
system_.GetFifo().SyncGPUForRegisterAccess(system_);
cp.SetCpStatusRegister(system_);
system_.GetFifo().SyncGPUForRegisterAccess();
cp.SetCpStatusRegister();
return cp.m_cp_status_reg.Hex;
}),
MMIO::InvalidWrite<u16>());
@ -235,8 +239,8 @@ void CommandProcessorManager::RegisterMMIO(Core::System& system, MMIO::Mapping*
auto& cp = system_.GetCommandProcessor();
UCPCtrlReg tmp(val);
cp.m_cp_ctrl_reg.Hex = tmp.Hex;
cp.SetCpControlRegister(system_);
system_.GetFifo().RunGpu(system_);
cp.SetCpControlRegister();
system_.GetFifo().RunGpu();
}));
mmio->Register(base | CLEAR_REGISTER, MMIO::DirectRead<u16>(&m_cp_clear_reg.Hex),
@ -245,13 +249,13 @@ void CommandProcessorManager::RegisterMMIO(Core::System& system, MMIO::Mapping*
UCPClearReg tmp(val);
cp.m_cp_clear_reg.Hex = tmp.Hex;
cp.SetCpClearRegister();
system_.GetFifo().RunGpu(system_);
system_.GetFifo().RunGpu();
}));
mmio->Register(base | PERF_SELECT, MMIO::InvalidRead<u16>(), MMIO::Nop<u16>());
// Some MMIOs have different handlers for single core vs. dual core mode.
const bool is_on_thread = IsOnThread(system);
const bool is_on_thread = IsOnThread(m_system);
MMIO::ReadHandlingMethod<u16>* fifo_rw_distance_lo_r;
if (is_on_thread)
{
@ -274,10 +278,11 @@ void CommandProcessorManager::RegisterMMIO(Core::System& system, MMIO::Mapping*
}
else
{
fifo_rw_distance_lo_r = MMIO::DirectRead<u16>(MMIO::Utils::LowPart(&fifo.CPReadWriteDistance));
fifo_rw_distance_lo_r =
MMIO::DirectRead<u16>(MMIO::Utils::LowPart(&m_fifo.CPReadWriteDistance));
}
mmio->Register(base | FIFO_RW_DISTANCE_LO, fifo_rw_distance_lo_r,
MMIO::DirectWrite<u16>(MMIO::Utils::LowPart(&fifo.CPReadWriteDistance),
MMIO::DirectWrite<u16>(MMIO::Utils::LowPart(&m_fifo.CPReadWriteDistance),
WMASK_LO_ALIGN_32BIT));
MMIO::ReadHandlingMethod<u16>* fifo_rw_distance_hi_r;
@ -285,7 +290,7 @@ void CommandProcessorManager::RegisterMMIO(Core::System& system, MMIO::Mapping*
{
fifo_rw_distance_hi_r = MMIO::ComplexRead<u16>([](Core::System& system_, u32) {
const auto& fifo_ = system_.GetCommandProcessor().GetFifo();
system_.GetFifo().SyncGPUForRegisterAccess(system_);
system_.GetFifo().SyncGPUForRegisterAccess();
if (fifo_.CPWritePointer.load(std::memory_order_relaxed) >=
fifo_.SafeCPReadPointer.load(std::memory_order_relaxed))
{
@ -307,23 +312,23 @@ void CommandProcessorManager::RegisterMMIO(Core::System& system, MMIO::Mapping*
{
fifo_rw_distance_hi_r = MMIO::ComplexRead<u16>([](Core::System& system_, u32) {
const auto& fifo_ = system_.GetCommandProcessor().GetFifo();
system_.GetFifo().SyncGPUForRegisterAccess(system_);
system_.GetFifo().SyncGPUForRegisterAccess();
return fifo_.CPReadWriteDistance.load(std::memory_order_relaxed) >> 16;
});
}
mmio->Register(base | FIFO_RW_DISTANCE_HI, fifo_rw_distance_hi_r,
MMIO::ComplexWrite<u16>([WMASK_HI_RESTRICT](Core::System& system_, u32, u16 val) {
auto& fifo_ = system_.GetCommandProcessor().GetFifo();
system_.GetFifo().SyncGPUForRegisterAccess(system_);
system_.GetFifo().SyncGPUForRegisterAccess();
WriteHigh(fifo_.CPReadWriteDistance, val & WMASK_HI_RESTRICT);
system_.GetFifo().RunGpu(system_);
system_.GetFifo().RunGpu();
}));
mmio->Register(
base | FIFO_READ_POINTER_LO,
is_on_thread ? MMIO::DirectRead<u16>(MMIO::Utils::LowPart(&fifo.SafeCPReadPointer)) :
MMIO::DirectRead<u16>(MMIO::Utils::LowPart(&fifo.CPReadPointer)),
MMIO::DirectWrite<u16>(MMIO::Utils::LowPart(&fifo.CPReadPointer), WMASK_LO_ALIGN_32BIT));
is_on_thread ? MMIO::DirectRead<u16>(MMIO::Utils::LowPart(&m_fifo.SafeCPReadPointer)) :
MMIO::DirectRead<u16>(MMIO::Utils::LowPart(&m_fifo.CPReadPointer)),
MMIO::DirectWrite<u16>(MMIO::Utils::LowPart(&m_fifo.CPReadPointer), WMASK_LO_ALIGN_32BIT));
MMIO::ReadHandlingMethod<u16>* fifo_read_hi_r;
MMIO::WriteHandlingMethod<u16>* fifo_read_hi_w;
@ -331,13 +336,13 @@ void CommandProcessorManager::RegisterMMIO(Core::System& system, MMIO::Mapping*
{
fifo_read_hi_r = MMIO::ComplexRead<u16>([](Core::System& system_, u32) {
auto& fifo_ = system_.GetCommandProcessor().GetFifo();
system_.GetFifo().SyncGPUForRegisterAccess(system_);
system_.GetFifo().SyncGPUForRegisterAccess();
return fifo_.SafeCPReadPointer.load(std::memory_order_relaxed) >> 16;
});
fifo_read_hi_w =
MMIO::ComplexWrite<u16>([WMASK_HI_RESTRICT](Core::System& system_, u32, u16 val) {
auto& fifo_ = system_.GetCommandProcessor().GetFifo();
system_.GetFifo().SyncGPUForRegisterAccess(system_);
system_.GetFifo().SyncGPUForRegisterAccess();
WriteHigh(fifo_.CPReadPointer, val & WMASK_HI_RESTRICT);
fifo_.SafeCPReadPointer.store(fifo_.CPReadPointer.load(std::memory_order_relaxed),
std::memory_order_relaxed);
@ -347,116 +352,114 @@ void CommandProcessorManager::RegisterMMIO(Core::System& system, MMIO::Mapping*
{
fifo_read_hi_r = MMIO::ComplexRead<u16>([](Core::System& system_, u32) {
const auto& fifo_ = system_.GetCommandProcessor().GetFifo();
system_.GetFifo().SyncGPUForRegisterAccess(system_);
system_.GetFifo().SyncGPUForRegisterAccess();
return fifo_.CPReadPointer.load(std::memory_order_relaxed) >> 16;
});
fifo_read_hi_w =
MMIO::ComplexWrite<u16>([WMASK_HI_RESTRICT](Core::System& system_, u32, u16 val) {
auto& fifo_ = system_.GetCommandProcessor().GetFifo();
system_.GetFifo().SyncGPUForRegisterAccess(system_);
system_.GetFifo().SyncGPUForRegisterAccess();
WriteHigh(fifo_.CPReadPointer, val & WMASK_HI_RESTRICT);
});
}
mmio->Register(base | FIFO_READ_POINTER_HI, fifo_read_hi_r, fifo_read_hi_w);
}
void CommandProcessorManager::GatherPipeBursted(Core::System& system)
void CommandProcessorManager::GatherPipeBursted()
{
auto& fifo = m_fifo;
SetCPStatusFromCPU();
SetCPStatusFromCPU(system);
auto& processor_interface = system.GetProcessorInterface();
auto& processor_interface = m_system.GetProcessorInterface();
// if we aren't linked, we don't care about gather pipe data
if (!m_cp_ctrl_reg.GPLinkEnable)
{
if (IsOnThread(system) && !system.GetFifo().UseDeterministicGPUThread())
if (IsOnThread(m_system) && !m_system.GetFifo().UseDeterministicGPUThread())
{
// In multibuffer mode is not allowed write in the same FIFO attached to the GPU.
// Fix Pokemon XD in DC mode.
if ((processor_interface.m_fifo_cpu_end == fifo.CPEnd.load(std::memory_order_relaxed)) &&
(processor_interface.m_fifo_cpu_base == fifo.CPBase.load(std::memory_order_relaxed)) &&
fifo.CPReadWriteDistance.load(std::memory_order_relaxed) > 0)
if ((processor_interface.m_fifo_cpu_end == m_fifo.CPEnd.load(std::memory_order_relaxed)) &&
(processor_interface.m_fifo_cpu_base == m_fifo.CPBase.load(std::memory_order_relaxed)) &&
m_fifo.CPReadWriteDistance.load(std::memory_order_relaxed) > 0)
{
system.GetFifo().FlushGpu(system);
m_system.GetFifo().FlushGpu();
}
}
system.GetFifo().RunGpu(system);
m_system.GetFifo().RunGpu();
return;
}
// update the fifo pointer
if (fifo.CPWritePointer.load(std::memory_order_relaxed) ==
fifo.CPEnd.load(std::memory_order_relaxed))
if (m_fifo.CPWritePointer.load(std::memory_order_relaxed) ==
m_fifo.CPEnd.load(std::memory_order_relaxed))
{
fifo.CPWritePointer.store(fifo.CPBase, std::memory_order_relaxed);
m_fifo.CPWritePointer.store(m_fifo.CPBase, std::memory_order_relaxed);
}
else
{
fifo.CPWritePointer.fetch_add(GPFifo::GATHER_PIPE_SIZE, std::memory_order_relaxed);
m_fifo.CPWritePointer.fetch_add(GPFifo::GATHER_PIPE_SIZE, std::memory_order_relaxed);
}
if (m_cp_ctrl_reg.GPReadEnable && m_cp_ctrl_reg.GPLinkEnable)
{
processor_interface.m_fifo_cpu_write_pointer =
fifo.CPWritePointer.load(std::memory_order_relaxed);
processor_interface.m_fifo_cpu_base = fifo.CPBase.load(std::memory_order_relaxed);
processor_interface.m_fifo_cpu_end = fifo.CPEnd.load(std::memory_order_relaxed);
m_fifo.CPWritePointer.load(std::memory_order_relaxed);
processor_interface.m_fifo_cpu_base = m_fifo.CPBase.load(std::memory_order_relaxed);
processor_interface.m_fifo_cpu_end = m_fifo.CPEnd.load(std::memory_order_relaxed);
}
// If the game is running close to overflowing, make the exception checking more frequent.
if (fifo.bFF_HiWatermark.load(std::memory_order_relaxed) != 0)
system.GetCoreTiming().ForceExceptionCheck(0);
if (m_fifo.bFF_HiWatermark.load(std::memory_order_relaxed) != 0)
m_system.GetCoreTiming().ForceExceptionCheck(0);
fifo.CPReadWriteDistance.fetch_add(GPFifo::GATHER_PIPE_SIZE, std::memory_order_seq_cst);
m_fifo.CPReadWriteDistance.fetch_add(GPFifo::GATHER_PIPE_SIZE, std::memory_order_seq_cst);
system.GetFifo().RunGpu(system);
m_system.GetFifo().RunGpu();
ASSERT_MSG(COMMANDPROCESSOR,
fifo.CPReadWriteDistance.load(std::memory_order_relaxed) <=
fifo.CPEnd.load(std::memory_order_relaxed) -
fifo.CPBase.load(std::memory_order_relaxed),
m_fifo.CPReadWriteDistance.load(std::memory_order_relaxed) <=
m_fifo.CPEnd.load(std::memory_order_relaxed) -
m_fifo.CPBase.load(std::memory_order_relaxed),
"FIFO is overflowed by GatherPipe !\nCPU thread is too fast!");
// check if we are in sync
ASSERT_MSG(COMMANDPROCESSOR,
fifo.CPWritePointer.load(std::memory_order_relaxed) ==
m_fifo.CPWritePointer.load(std::memory_order_relaxed) ==
processor_interface.m_fifo_cpu_write_pointer,
"FIFOs linked but out of sync");
ASSERT_MSG(COMMANDPROCESSOR,
fifo.CPBase.load(std::memory_order_relaxed) == processor_interface.m_fifo_cpu_base,
m_fifo.CPBase.load(std::memory_order_relaxed) == processor_interface.m_fifo_cpu_base,
"FIFOs linked but out of sync");
ASSERT_MSG(COMMANDPROCESSOR,
fifo.CPEnd.load(std::memory_order_relaxed) == processor_interface.m_fifo_cpu_end,
m_fifo.CPEnd.load(std::memory_order_relaxed) == processor_interface.m_fifo_cpu_end,
"FIFOs linked but out of sync");
}
void CommandProcessorManager::UpdateInterrupts(Core::System& system, u64 userdata)
void CommandProcessorManager::UpdateInterrupts(u64 userdata)
{
if (userdata)
{
m_interrupt_set.Set();
DEBUG_LOG_FMT(COMMANDPROCESSOR, "Interrupt set");
system.GetProcessorInterface().SetInterrupt(INT_CAUSE_CP, true);
m_system.GetProcessorInterface().SetInterrupt(INT_CAUSE_CP, true);
}
else
{
m_interrupt_set.Clear();
DEBUG_LOG_FMT(COMMANDPROCESSOR, "Interrupt cleared");
system.GetProcessorInterface().SetInterrupt(INT_CAUSE_CP, false);
m_system.GetProcessorInterface().SetInterrupt(INT_CAUSE_CP, false);
}
system.GetCoreTiming().ForceExceptionCheck(0);
m_system.GetCoreTiming().ForceExceptionCheck(0);
m_interrupt_waiting.Clear();
system.GetFifo().RunGpu(system);
m_system.GetFifo().RunGpu();
}
void CommandProcessorManager::UpdateInterruptsFromVideoBackend(Core::System& system, u64 userdata)
void CommandProcessorManager::UpdateInterruptsFromVideoBackend(u64 userdata)
{
if (!system.GetFifo().UseDeterministicGPUThread())
if (!m_system.GetFifo().UseDeterministicGPUThread())
{
system.GetCoreTiming().ScheduleEvent(0, m_event_type_update_interrupts, userdata,
CoreTiming::FromThread::NON_CPU);
m_system.GetCoreTiming().ScheduleEvent(0, m_event_type_update_interrupts, userdata,
CoreTiming::FromThread::NON_CPU);
}
}
@ -465,22 +468,20 @@ bool CommandProcessorManager::IsInterruptWaiting() const
return m_interrupt_waiting.IsSet();
}
void CommandProcessorManager::SetCPStatusFromGPU(Core::System& system)
void CommandProcessorManager::SetCPStatusFromGPU()
{
auto& fifo = m_fifo;
// breakpoint
const bool breakpoint = fifo.bFF_Breakpoint.load(std::memory_order_relaxed);
if (fifo.bFF_BPEnable.load(std::memory_order_relaxed) != 0)
const bool breakpoint = m_fifo.bFF_Breakpoint.load(std::memory_order_relaxed);
if (m_fifo.bFF_BPEnable.load(std::memory_order_relaxed) != 0)
{
if (fifo.CPBreakpoint.load(std::memory_order_relaxed) ==
fifo.CPReadPointer.load(std::memory_order_relaxed))
if (m_fifo.CPBreakpoint.load(std::memory_order_relaxed) ==
m_fifo.CPReadPointer.load(std::memory_order_relaxed))
{
if (!breakpoint)
{
DEBUG_LOG_FMT(COMMANDPROCESSOR, "Hit breakpoint at {}",
fifo.CPReadPointer.load(std::memory_order_relaxed));
fifo.bFF_Breakpoint.store(1, std::memory_order_relaxed);
m_fifo.CPReadPointer.load(std::memory_order_relaxed));
m_fifo.bFF_Breakpoint.store(1, std::memory_order_relaxed);
}
}
else
@ -488,8 +489,8 @@ void CommandProcessorManager::SetCPStatusFromGPU(Core::System& system)
if (breakpoint)
{
DEBUG_LOG_FMT(COMMANDPROCESSOR, "Cleared breakpoint at {}",
fifo.CPReadPointer.load(std::memory_order_relaxed));
fifo.bFF_Breakpoint.store(0, std::memory_order_relaxed);
m_fifo.CPReadPointer.load(std::memory_order_relaxed));
m_fifo.bFF_Breakpoint.store(0, std::memory_order_relaxed);
}
}
}
@ -498,101 +499,97 @@ void CommandProcessorManager::SetCPStatusFromGPU(Core::System& system)
if (breakpoint)
{
DEBUG_LOG_FMT(COMMANDPROCESSOR, "Cleared breakpoint at {}",
fifo.CPReadPointer.load(std::memory_order_relaxed));
fifo.bFF_Breakpoint = false;
m_fifo.CPReadPointer.load(std::memory_order_relaxed));
m_fifo.bFF_Breakpoint = false;
}
}
// overflow & underflow check
fifo.bFF_HiWatermark.store(
(fifo.CPReadWriteDistance.load(std::memory_order_relaxed) > fifo.CPHiWatermark),
m_fifo.bFF_HiWatermark.store(
(m_fifo.CPReadWriteDistance.load(std::memory_order_relaxed) > m_fifo.CPHiWatermark),
std::memory_order_relaxed);
fifo.bFF_LoWatermark.store(
(fifo.CPReadWriteDistance.load(std::memory_order_relaxed) < fifo.CPLoWatermark),
m_fifo.bFF_LoWatermark.store(
(m_fifo.CPReadWriteDistance.load(std::memory_order_relaxed) < m_fifo.CPLoWatermark),
std::memory_order_relaxed);
bool bpInt = fifo.bFF_Breakpoint.load(std::memory_order_relaxed) &&
fifo.bFF_BPInt.load(std::memory_order_relaxed);
bool ovfInt = fifo.bFF_HiWatermark.load(std::memory_order_relaxed) &&
fifo.bFF_HiWatermarkInt.load(std::memory_order_relaxed);
bool undfInt = fifo.bFF_LoWatermark.load(std::memory_order_relaxed) &&
fifo.bFF_LoWatermarkInt.load(std::memory_order_relaxed);
bool bpInt = m_fifo.bFF_Breakpoint.load(std::memory_order_relaxed) &&
m_fifo.bFF_BPInt.load(std::memory_order_relaxed);
bool ovfInt = m_fifo.bFF_HiWatermark.load(std::memory_order_relaxed) &&
m_fifo.bFF_HiWatermarkInt.load(std::memory_order_relaxed);
bool undfInt = m_fifo.bFF_LoWatermark.load(std::memory_order_relaxed) &&
m_fifo.bFF_LoWatermarkInt.load(std::memory_order_relaxed);
bool interrupt = (bpInt || ovfInt || undfInt) && m_cp_ctrl_reg.GPReadEnable;
if (interrupt != m_interrupt_set.IsSet() && !m_interrupt_waiting.IsSet())
{
u64 userdata = interrupt ? 1 : 0;
if (IsOnThread(system))
const u64 userdata = interrupt ? 1 : 0;
if (IsOnThread(m_system))
{
if (!interrupt || bpInt || undfInt || ovfInt)
{
// Schedule the interrupt asynchronously
m_interrupt_waiting.Set();
UpdateInterruptsFromVideoBackend(system, userdata);
UpdateInterruptsFromVideoBackend(userdata);
}
}
else
{
UpdateInterrupts(system, userdata);
UpdateInterrupts(userdata);
}
}
}
void CommandProcessorManager::SetCPStatusFromCPU(Core::System& system)
void CommandProcessorManager::SetCPStatusFromCPU()
{
auto& fifo = m_fifo;
// overflow & underflow check
fifo.bFF_HiWatermark.store(
(fifo.CPReadWriteDistance.load(std::memory_order_relaxed) > fifo.CPHiWatermark),
m_fifo.bFF_HiWatermark.store(
(m_fifo.CPReadWriteDistance.load(std::memory_order_relaxed) > m_fifo.CPHiWatermark),
std::memory_order_relaxed);
fifo.bFF_LoWatermark.store(
(fifo.CPReadWriteDistance.load(std::memory_order_relaxed) < fifo.CPLoWatermark),
m_fifo.bFF_LoWatermark.store(
(m_fifo.CPReadWriteDistance.load(std::memory_order_relaxed) < m_fifo.CPLoWatermark),
std::memory_order_relaxed);
bool bpInt = fifo.bFF_Breakpoint.load(std::memory_order_relaxed) &&
fifo.bFF_BPInt.load(std::memory_order_relaxed);
bool ovfInt = fifo.bFF_HiWatermark.load(std::memory_order_relaxed) &&
fifo.bFF_HiWatermarkInt.load(std::memory_order_relaxed);
bool undfInt = fifo.bFF_LoWatermark.load(std::memory_order_relaxed) &&
fifo.bFF_LoWatermarkInt.load(std::memory_order_relaxed);
bool bpInt = m_fifo.bFF_Breakpoint.load(std::memory_order_relaxed) &&
m_fifo.bFF_BPInt.load(std::memory_order_relaxed);
bool ovfInt = m_fifo.bFF_HiWatermark.load(std::memory_order_relaxed) &&
m_fifo.bFF_HiWatermarkInt.load(std::memory_order_relaxed);
bool undfInt = m_fifo.bFF_LoWatermark.load(std::memory_order_relaxed) &&
m_fifo.bFF_LoWatermarkInt.load(std::memory_order_relaxed);
bool interrupt = (bpInt || ovfInt || undfInt) && m_cp_ctrl_reg.GPReadEnable;
if (interrupt != m_interrupt_set.IsSet() && !m_interrupt_waiting.IsSet())
{
u64 userdata = interrupt ? 1 : 0;
if (IsOnThread(system))
const u64 userdata = interrupt ? 1 : 0;
if (IsOnThread(m_system))
{
if (!interrupt || bpInt || undfInt || ovfInt)
{
m_interrupt_set.Set(interrupt);
DEBUG_LOG_FMT(COMMANDPROCESSOR, "Interrupt set");
system.GetProcessorInterface().SetInterrupt(INT_CAUSE_CP, interrupt);
m_system.GetProcessorInterface().SetInterrupt(INT_CAUSE_CP, interrupt);
}
}
else
{
UpdateInterrupts(system, userdata);
UpdateInterrupts(userdata);
}
}
}
void CommandProcessorManager::SetCpStatusRegister(Core::System& system)
void CommandProcessorManager::SetCpStatusRegister()
{
const auto& fifo = m_fifo;
// Here always there is one fifo attached to the GPU
m_cp_status_reg.Breakpoint = fifo.bFF_Breakpoint.load(std::memory_order_relaxed);
m_cp_status_reg.ReadIdle = !fifo.CPReadWriteDistance.load(std::memory_order_relaxed) ||
(fifo.CPReadPointer.load(std::memory_order_relaxed) ==
fifo.CPWritePointer.load(std::memory_order_relaxed));
m_cp_status_reg.CommandIdle = !fifo.CPReadWriteDistance.load(std::memory_order_relaxed) ||
Fifo::AtBreakpoint(system) ||
!fifo.bFF_GPReadEnable.load(std::memory_order_relaxed);
m_cp_status_reg.UnderflowLoWatermark = fifo.bFF_LoWatermark.load(std::memory_order_relaxed);
m_cp_status_reg.OverflowHiWatermark = fifo.bFF_HiWatermark.load(std::memory_order_relaxed);
m_cp_status_reg.Breakpoint = m_fifo.bFF_Breakpoint.load(std::memory_order_relaxed);
m_cp_status_reg.ReadIdle = !m_fifo.CPReadWriteDistance.load(std::memory_order_relaxed) ||
(m_fifo.CPReadPointer.load(std::memory_order_relaxed) ==
m_fifo.CPWritePointer.load(std::memory_order_relaxed));
m_cp_status_reg.CommandIdle = !m_fifo.CPReadWriteDistance.load(std::memory_order_relaxed) ||
Fifo::AtBreakpoint(m_system) ||
!m_fifo.bFF_GPReadEnable.load(std::memory_order_relaxed);
m_cp_status_reg.UnderflowLoWatermark = m_fifo.bFF_LoWatermark.load(std::memory_order_relaxed);
m_cp_status_reg.OverflowHiWatermark = m_fifo.bFF_HiWatermark.load(std::memory_order_relaxed);
DEBUG_LOG_FMT(COMMANDPROCESSOR, "\t Read from STATUS_REGISTER : {:04x}", m_cp_status_reg.Hex);
DEBUG_LOG_FMT(COMMANDPROCESSOR,
@ -603,30 +600,28 @@ void CommandProcessorManager::SetCpStatusRegister(Core::System& system)
m_cp_status_reg.UnderflowLoWatermark ? "ON" : "OFF");
}
void CommandProcessorManager::SetCpControlRegister(Core::System& system)
void CommandProcessorManager::SetCpControlRegister()
{
auto& fifo = m_fifo;
m_fifo.bFF_BPInt.store(m_cp_ctrl_reg.BPInt, std::memory_order_relaxed);
m_fifo.bFF_BPEnable.store(m_cp_ctrl_reg.BPEnable, std::memory_order_relaxed);
m_fifo.bFF_HiWatermarkInt.store(m_cp_ctrl_reg.FifoOverflowIntEnable, std::memory_order_relaxed);
m_fifo.bFF_LoWatermarkInt.store(m_cp_ctrl_reg.FifoUnderflowIntEnable, std::memory_order_relaxed);
m_fifo.bFF_GPLinkEnable.store(m_cp_ctrl_reg.GPLinkEnable, std::memory_order_relaxed);
fifo.bFF_BPInt.store(m_cp_ctrl_reg.BPInt, std::memory_order_relaxed);
fifo.bFF_BPEnable.store(m_cp_ctrl_reg.BPEnable, std::memory_order_relaxed);
fifo.bFF_HiWatermarkInt.store(m_cp_ctrl_reg.FifoOverflowIntEnable, std::memory_order_relaxed);
fifo.bFF_LoWatermarkInt.store(m_cp_ctrl_reg.FifoUnderflowIntEnable, std::memory_order_relaxed);
fifo.bFF_GPLinkEnable.store(m_cp_ctrl_reg.GPLinkEnable, std::memory_order_relaxed);
if (fifo.bFF_GPReadEnable.load(std::memory_order_relaxed) && !m_cp_ctrl_reg.GPReadEnable)
if (m_fifo.bFF_GPReadEnable.load(std::memory_order_relaxed) && !m_cp_ctrl_reg.GPReadEnable)
{
fifo.bFF_GPReadEnable.store(m_cp_ctrl_reg.GPReadEnable, std::memory_order_relaxed);
system.GetFifo().FlushGpu(system);
m_fifo.bFF_GPReadEnable.store(m_cp_ctrl_reg.GPReadEnable, std::memory_order_relaxed);
m_system.GetFifo().FlushGpu();
}
else
{
fifo.bFF_GPReadEnable = m_cp_ctrl_reg.GPReadEnable;
m_fifo.bFF_GPReadEnable = m_cp_ctrl_reg.GPReadEnable;
}
DEBUG_LOG_FMT(COMMANDPROCESSOR, "\t GPREAD {} | BP {} | Int {} | OvF {} | UndF {} | LINK {}",
fifo.bFF_GPReadEnable.load(std::memory_order_relaxed) ? "ON" : "OFF",
fifo.bFF_BPEnable.load(std::memory_order_relaxed) ? "ON" : "OFF",
fifo.bFF_BPInt.load(std::memory_order_relaxed) ? "ON" : "OFF",
m_fifo.bFF_GPReadEnable.load(std::memory_order_relaxed) ? "ON" : "OFF",
m_fifo.bFF_BPEnable.load(std::memory_order_relaxed) ? "ON" : "OFF",
m_fifo.bFF_BPInt.load(std::memory_order_relaxed) ? "ON" : "OFF",
m_cp_ctrl_reg.FifoOverflowIntEnable ? "ON" : "OFF",
m_cp_ctrl_reg.FifoUnderflowIntEnable ? "ON" : "OFF",
m_cp_ctrl_reg.GPLinkEnable ? "ON" : "OFF");
@ -638,11 +633,8 @@ void CommandProcessorManager::SetCpClearRegister()
{
}
void CommandProcessorManager::HandleUnknownOpcode(Core::System& system, u8 cmd_byte,
const u8* buffer, bool preprocess)
void CommandProcessorManager::HandleUnknownOpcode(u8 cmd_byte, const u8* buffer, bool preprocess)
{
const auto& fifo = m_fifo;
// Datel software uses 0x01 during startup, and Mario Party 5's Wiggler capsule accidentally uses
// 0x01-0x03 due to sending 4 more vertices than intended (see https://dolp.in/i8104).
// Prince of Persia: Rival Swords sends 0x3f if the home menu is opened during the intro cutscene
@ -667,7 +659,7 @@ void CommandProcessorManager::HandleUnknownOpcode(Core::System& system, u8 cmd_b
// PC and LR are meaningless when using the fifoplayer, and will generally not be helpful if the
// unknown opcode is inside of a display list. Also note that the changes in GPFifo.h are not
// accurate and may introduce timing issues.
const auto& ppc_state = system.GetPPCState();
const auto& ppc_state = m_system.GetPPCState();
GENERIC_LOG_FMT(
Common::Log::LogType::VIDEO, log_level,
"FIFO: Unknown Opcode {:#04x} @ {}, preprocessing = {}, CPBase: {:#010x}, CPEnd: "
@ -677,19 +669,19 @@ void CommandProcessorManager::HandleUnknownOpcode(Core::System& system, u8 cmd_b
"{}, bFF_GPLinkEnable: {}, bFF_HiWatermarkInt: {}, bFF_LoWatermarkInt: {}, "
"approximate PC: {:08x}, approximate LR: {:08x}",
cmd_byte, fmt::ptr(buffer), preprocess ? "yes" : "no",
fifo.CPBase.load(std::memory_order_relaxed), fifo.CPEnd.load(std::memory_order_relaxed),
fifo.CPHiWatermark, fifo.CPLoWatermark,
fifo.CPReadWriteDistance.load(std::memory_order_relaxed),
fifo.CPWritePointer.load(std::memory_order_relaxed),
fifo.CPReadPointer.load(std::memory_order_relaxed),
fifo.CPBreakpoint.load(std::memory_order_relaxed),
fifo.bFF_GPReadEnable.load(std::memory_order_relaxed) ? "true" : "false",
fifo.bFF_BPEnable.load(std::memory_order_relaxed) ? "true" : "false",
fifo.bFF_BPInt.load(std::memory_order_relaxed) ? "true" : "false",
fifo.bFF_Breakpoint.load(std::memory_order_relaxed) ? "true" : "false",
fifo.bFF_GPLinkEnable.load(std::memory_order_relaxed) ? "true" : "false",
fifo.bFF_HiWatermarkInt.load(std::memory_order_relaxed) ? "true" : "false",
fifo.bFF_LoWatermarkInt.load(std::memory_order_relaxed) ? "true" : "false", ppc_state.pc,
m_fifo.CPBase.load(std::memory_order_relaxed), m_fifo.CPEnd.load(std::memory_order_relaxed),
m_fifo.CPHiWatermark, m_fifo.CPLoWatermark,
m_fifo.CPReadWriteDistance.load(std::memory_order_relaxed),
m_fifo.CPWritePointer.load(std::memory_order_relaxed),
m_fifo.CPReadPointer.load(std::memory_order_relaxed),
m_fifo.CPBreakpoint.load(std::memory_order_relaxed),
m_fifo.bFF_GPReadEnable.load(std::memory_order_relaxed) ? "true" : "false",
m_fifo.bFF_BPEnable.load(std::memory_order_relaxed) ? "true" : "false",
m_fifo.bFF_BPInt.load(std::memory_order_relaxed) ? "true" : "false",
m_fifo.bFF_Breakpoint.load(std::memory_order_relaxed) ? "true" : "false",
m_fifo.bFF_GPLinkEnable.load(std::memory_order_relaxed) ? "true" : "false",
m_fifo.bFF_HiWatermarkInt.load(std::memory_order_relaxed) ? "true" : "false",
m_fifo.bFF_LoWatermarkInt.load(std::memory_order_relaxed) ? "true" : "false", ppc_state.pc,
LR(ppc_state));
if (!m_is_fifo_error_seen && !suppress_panic_alert)
@ -699,9 +691,9 @@ void CommandProcessorManager::HandleUnknownOpcode(Core::System& system, u8 cmd_b
// The panic alert contains an explanatory part that's worded differently depending on the
// user's settings, so as to offer the most relevant advice to the user.
const char* advice;
if (IsOnThread(system) && !system.GetFifo().UseDeterministicGPUThread())
if (IsOnThread(m_system) && !m_system.GetFifo().UseDeterministicGPUThread())
{
if (!system.GetCoreTiming().UseSyncOnSkipIdle() && !system.GetFifo().UseSyncGPU())
if (!m_system.GetCoreTiming().UseSyncOnSkipIdle() && !m_system.GetFifo().UseSyncGPU())
{
// The SyncOnSkipIdle setting is only in the Android GUI, so we use the INI name on other platforms.
//

View file

@ -160,24 +160,26 @@ u32 GetPhysicalAddressMask();
class CommandProcessorManager
{
public:
void Init(Core::System& system);
explicit CommandProcessorManager(Core::System& system);
void Init();
void DoState(PointerWrap& p);
void RegisterMMIO(Core::System& system, MMIO::Mapping* mmio, u32 base);
void RegisterMMIO(MMIO::Mapping* mmio, u32 base);
void SetCPStatusFromGPU(Core::System& system);
void SetCPStatusFromCPU(Core::System& system);
void GatherPipeBursted(Core::System& system);
void UpdateInterrupts(Core::System& system, u64 userdata);
void UpdateInterruptsFromVideoBackend(Core::System& system, u64 userdata);
void SetCPStatusFromGPU();
void SetCPStatusFromCPU();
void GatherPipeBursted();
void UpdateInterrupts(u64 userdata);
void UpdateInterruptsFromVideoBackend(u64 userdata);
bool IsInterruptWaiting() const;
void SetCpClearRegister();
void SetCpControlRegister(Core::System& system);
void SetCpStatusRegister(Core::System& system);
void SetCpControlRegister();
void SetCpStatusRegister();
void HandleUnknownOpcode(Core::System& system, u8 cmd_byte, const u8* buffer, bool preprocess);
void HandleUnknownOpcode(u8 cmd_byte, const u8* buffer, bool preprocess);
// This one is shared between gfx thread and emulator thread.
// It is only used by the Fifo and by the CommandProcessor.
@ -203,6 +205,8 @@ private:
Common::Flag m_interrupt_waiting;
bool m_is_fifo_error_seen = false;
Core::System& m_system;
};
} // namespace CommandProcessor

View file

@ -7,6 +7,13 @@
namespace VideoCommon
{
#ifdef ANDROID
// Some devices seem to have graphical errors when providing 16 pixel samplers
// given the logic is for a performance heavy feature (custom shaders), will just disable for now
// TODO: handle this more elegantly
constexpr u32 MAX_PIXEL_SHADER_SAMPLERS = 8;
#else
constexpr u32 MAX_PIXEL_SHADER_SAMPLERS = 16;
#endif
constexpr u32 MAX_COMPUTE_SHADER_SAMPLERS = 8;
} // namespace VideoCommon

View file

@ -36,7 +36,10 @@ namespace Fifo
{
static constexpr int GPU_TIME_SLOT_SIZE = 1000;
FifoManager::FifoManager() = default;
FifoManager::FifoManager(Core::System& system) : m_system{system}
{
}
FifoManager::~FifoManager() = default;
void FifoManager::RefreshConfig()
@ -64,26 +67,26 @@ void FifoManager::DoState(PointerWrap& p)
p.Do(m_syncing_suspended);
}
void FifoManager::PauseAndLock(Core::System& system, bool doLock, bool unpauseOnUnlock)
void FifoManager::PauseAndLock(bool do_lock, bool unpause_on_unlock)
{
if (doLock)
if (do_lock)
{
SyncGPU(SyncGPUReason::Other);
EmulatorState(false);
if (!system.IsDualCoreMode() || m_use_deterministic_gpu_thread)
if (!m_system.IsDualCoreMode() || m_use_deterministic_gpu_thread)
return;
m_gpu_mainloop.WaitYield(std::chrono::milliseconds(100), Host_YieldToUI);
}
else
{
if (unpauseOnUnlock)
if (unpause_on_unlock)
EmulatorState(true);
}
}
void FifoManager::Init(Core::System& system)
void FifoManager::Init()
{
if (!m_config_callback_id)
m_config_callback_id = Config::AddConfigChangedCallback([this] { RefreshConfig(); });
@ -92,7 +95,7 @@ void FifoManager::Init(Core::System& system)
// Padded so that SIMD overreads in the vertex loader are safe
m_video_buffer = static_cast<u8*>(Common::AllocateMemoryPages(FIFO_SIZE + 4));
ResetVideoBuffer();
if (system.IsDualCoreMode())
if (m_system.IsDualCoreMode())
m_gpu_mainloop.Prepare();
m_sync_ticks.store(0);
}
@ -120,14 +123,14 @@ void FifoManager::Shutdown()
// May be executed from any thread, even the graphics thread.
// Created to allow for self shutdown.
void FifoManager::ExitGpuLoop(Core::System& system)
void FifoManager::ExitGpuLoop()
{
auto& command_processor = system.GetCommandProcessor();
auto& command_processor = m_system.GetCommandProcessor();
auto& fifo = command_processor.GetFifo();
// This should break the wait loop in CPU thread
fifo.bFF_GPReadEnable.store(0, std::memory_order_relaxed);
FlushGpu(system);
FlushGpu();
// Terminate GPU thread loop
m_emu_running_state.Set();
@ -211,7 +214,7 @@ void* FifoManager::PopFifoAuxBuffer(size_t size)
}
// Description: RunGpuLoop() sends data through this function.
void FifoManager::ReadDataFromFifo(Core::System& system, u32 readPtr)
void FifoManager::ReadDataFromFifo(u32 read_ptr)
{
if (GPFifo::GATHER_PIPE_SIZE >
static_cast<size_t>(m_video_buffer + FIFO_SIZE - m_video_buffer_write_ptr))
@ -228,13 +231,13 @@ void FifoManager::ReadDataFromFifo(Core::System& system, u32 readPtr)
m_video_buffer_read_ptr = m_video_buffer;
}
// Copy new video instructions to m_video_buffer for future use in rendering the new picture
auto& memory = system.GetMemory();
memory.CopyFromEmu(m_video_buffer_write_ptr, readPtr, GPFifo::GATHER_PIPE_SIZE);
auto& memory = m_system.GetMemory();
memory.CopyFromEmu(m_video_buffer_write_ptr, read_ptr, GPFifo::GATHER_PIPE_SIZE);
m_video_buffer_write_ptr += GPFifo::GATHER_PIPE_SIZE;
}
// The deterministic_gpu_thread version.
void FifoManager::ReadDataFromFifoOnCPU(Core::System& system, u32 readPtr)
void FifoManager::ReadDataFromFifoOnCPU(u32 read_ptr)
{
u8* write_ptr = m_video_buffer_write_ptr;
if (GPFifo::GATHER_PIPE_SIZE > static_cast<size_t>(m_video_buffer + FIFO_SIZE - write_ptr))
@ -262,8 +265,8 @@ void FifoManager::ReadDataFromFifoOnCPU(Core::System& system, u32 readPtr)
return;
}
}
auto& memory = system.GetMemory();
memory.CopyFromEmu(m_video_buffer_write_ptr, readPtr, GPFifo::GATHER_PIPE_SIZE);
auto& memory = m_system.GetMemory();
memory.CopyFromEmu(m_video_buffer_write_ptr, read_ptr, GPFifo::GATHER_PIPE_SIZE);
m_video_buffer_pp_read_ptr = OpcodeDecoder::RunFifo<true>(
DataReader(m_video_buffer_pp_read_ptr, write_ptr + GPFifo::GATHER_PIPE_SIZE), nullptr);
// This would have to be locked if the GPU thread didn't spin.
@ -282,13 +285,13 @@ void FifoManager::ResetVideoBuffer()
// Description: Main FIFO update loop
// Purpose: Keep the Core HW updated about the CPU-GPU distance
void FifoManager::RunGpuLoop(Core::System& system)
void FifoManager::RunGpuLoop()
{
AsyncRequests::GetInstance()->SetEnable(true);
AsyncRequests::GetInstance()->SetPassthrough(false);
m_gpu_mainloop.Run(
[this, &system] {
[this] {
// Run events from the CPU thread.
AsyncRequests::GetInstance()->PullEvents();
@ -311,21 +314,22 @@ void FifoManager::RunGpuLoop(Core::System& system)
}
else
{
auto& command_processor = system.GetCommandProcessor();
auto& command_processor = m_system.GetCommandProcessor();
auto& fifo = command_processor.GetFifo();
command_processor.SetCPStatusFromGPU(system);
command_processor.SetCPStatusFromGPU();
// check if we are able to run this buffer
while (!command_processor.IsInterruptWaiting() &&
fifo.bFF_GPReadEnable.load(std::memory_order_relaxed) &&
fifo.CPReadWriteDistance.load(std::memory_order_relaxed) && !AtBreakpoint(system))
fifo.CPReadWriteDistance.load(std::memory_order_relaxed) &&
!AtBreakpoint(m_system))
{
if (m_config_sync_gpu && m_sync_ticks.load() < m_config_sync_gpu_min_distance)
break;
u32 cyclesExecuted = 0;
u32 readPtr = fifo.CPReadPointer.load(std::memory_order_relaxed);
ReadDataFromFifo(system, readPtr);
ReadDataFromFifo(readPtr);
if (readPtr == fifo.CPEnd.load(std::memory_order_relaxed))
readPtr = fifo.CPBase.load(std::memory_order_relaxed);
@ -352,7 +356,7 @@ void FifoManager::RunGpuLoop(Core::System& system)
std::memory_order_relaxed);
}
command_processor.SetCPStatusFromGPU(system);
command_processor.SetCPStatusFromGPU();
if (m_config_sync_gpu)
{
@ -392,9 +396,9 @@ void FifoManager::RunGpuLoop(Core::System& system)
AsyncRequests::GetInstance()->SetPassthrough(true);
}
void FifoManager::FlushGpu(Core::System& system)
void FifoManager::FlushGpu()
{
if (!system.IsDualCoreMode() || m_use_deterministic_gpu_thread)
if (!m_system.IsDualCoreMode() || m_use_deterministic_gpu_thread)
return;
m_gpu_mainloop.Wait();
@ -414,9 +418,9 @@ bool AtBreakpoint(Core::System& system)
fifo.CPBreakpoint.load(std::memory_order_relaxed));
}
void FifoManager::RunGpu(Core::System& system)
void FifoManager::RunGpu()
{
const bool is_dual_core = system.IsDualCoreMode();
const bool is_dual_core = m_system.IsDualCoreMode();
// wake up GPU thread
if (is_dual_core && !m_use_deterministic_gpu_thread)
@ -430,25 +434,25 @@ void FifoManager::RunGpu(Core::System& system)
if (m_syncing_suspended)
{
m_syncing_suspended = false;
system.GetCoreTiming().ScheduleEvent(GPU_TIME_SLOT_SIZE, m_event_sync_gpu,
GPU_TIME_SLOT_SIZE);
m_system.GetCoreTiming().ScheduleEvent(GPU_TIME_SLOT_SIZE, m_event_sync_gpu,
GPU_TIME_SLOT_SIZE);
}
}
}
int FifoManager::RunGpuOnCpu(Core::System& system, int ticks)
int FifoManager::RunGpuOnCpu(int ticks)
{
auto& command_processor = system.GetCommandProcessor();
auto& command_processor = m_system.GetCommandProcessor();
auto& fifo = command_processor.GetFifo();
bool reset_simd_state = false;
int available_ticks = int(ticks * m_config_sync_gpu_overclock) + m_sync_ticks.load();
while (fifo.bFF_GPReadEnable.load(std::memory_order_relaxed) &&
fifo.CPReadWriteDistance.load(std::memory_order_relaxed) && !AtBreakpoint(system) &&
fifo.CPReadWriteDistance.load(std::memory_order_relaxed) && !AtBreakpoint(m_system) &&
available_ticks >= 0)
{
if (m_use_deterministic_gpu_thread)
{
ReadDataFromFifoOnCPU(system, fifo.CPReadPointer.load(std::memory_order_relaxed));
ReadDataFromFifoOnCPU(fifo.CPReadPointer.load(std::memory_order_relaxed));
m_gpu_mainloop.Wakeup();
}
else
@ -459,7 +463,7 @@ int FifoManager::RunGpuOnCpu(Core::System& system, int ticks)
Common::FPU::LoadDefaultSIMDState();
reset_simd_state = true;
}
ReadDataFromFifo(system, fifo.CPReadPointer.load(std::memory_order_relaxed));
ReadDataFromFifo(fifo.CPReadPointer.load(std::memory_order_relaxed));
u32 cycles = 0;
m_video_buffer_read_ptr = OpcodeDecoder::RunFifo(
DataReader(m_video_buffer_read_ptr, m_video_buffer_write_ptr), &cycles);
@ -480,7 +484,7 @@ int FifoManager::RunGpuOnCpu(Core::System& system, int ticks)
fifo.CPReadWriteDistance.fetch_sub(GPFifo::GATHER_PIPE_SIZE, std::memory_order_relaxed);
}
command_processor.SetCPStatusFromGPU(system);
command_processor.SetCPStatusFromGPU();
if (reset_simd_state)
{
@ -498,7 +502,7 @@ int FifoManager::RunGpuOnCpu(Core::System& system, int ticks)
return -available_ticks + GPU_TIME_SLOT_SIZE;
}
void FifoManager::UpdateWantDeterminism(Core::System& system, bool want)
void FifoManager::UpdateWantDeterminism(bool want)
{
// We are paused (or not running at all yet), so
// it should be safe to change this.
@ -516,7 +520,7 @@ void FifoManager::UpdateWantDeterminism(Core::System& system, bool want)
break;
}
gpu_thread = gpu_thread && system.IsDualCoreMode();
gpu_thread = gpu_thread && m_system.IsDualCoreMode();
if (m_use_deterministic_gpu_thread != gpu_thread)
{
@ -536,7 +540,7 @@ void FifoManager::UpdateWantDeterminism(Core::System& system, bool want)
* @ticks The gone emulated CPU time.
* @return A good time to call WaitForGpuThread() next.
*/
int FifoManager::WaitForGpuThread(Core::System& system, int ticks)
int FifoManager::WaitForGpuThread(int ticks)
{
int old = m_sync_ticks.fetch_add(ticks);
int now = old + ticks;
@ -547,7 +551,7 @@ int FifoManager::WaitForGpuThread(Core::System& system, int ticks)
// Wakeup GPU
if (old < m_config_sync_gpu_min_distance && now >= m_config_sync_gpu_min_distance)
RunGpu(system);
RunGpu();
// If the GPU is still sleeping, wait for a longer time
if (now < m_config_sync_gpu_min_distance)
@ -568,11 +572,11 @@ void FifoManager::SyncGPUCallback(Core::System& system, u64 ticks, s64 cyclesLat
auto& fifo = system.GetFifo();
if (!system.IsDualCoreMode() || fifo.m_use_deterministic_gpu_thread)
{
next = fifo.RunGpuOnCpu(system, (int)ticks);
next = fifo.RunGpuOnCpu(int(ticks));
}
else if (fifo.m_config_sync_gpu)
{
next = fifo.WaitForGpuThread(system, (int)ticks);
next = fifo.WaitForGpuThread(int(ticks));
}
fifo.m_syncing_suspended = next < 0;
@ -580,20 +584,20 @@ void FifoManager::SyncGPUCallback(Core::System& system, u64 ticks, s64 cyclesLat
system.GetCoreTiming().ScheduleEvent(next, fifo.m_event_sync_gpu, next);
}
void FifoManager::SyncGPUForRegisterAccess(Core::System& system)
void FifoManager::SyncGPUForRegisterAccess()
{
SyncGPU(SyncGPUReason::Other);
if (!system.IsDualCoreMode() || m_use_deterministic_gpu_thread)
RunGpuOnCpu(system, GPU_TIME_SLOT_SIZE);
if (!m_system.IsDualCoreMode() || m_use_deterministic_gpu_thread)
RunGpuOnCpu(GPU_TIME_SLOT_SIZE);
else if (m_config_sync_gpu)
WaitForGpuThread(system, GPU_TIME_SLOT_SIZE);
WaitForGpuThread(GPU_TIME_SLOT_SIZE);
}
// Initialize GPU - CPU thread syncing, this gives us a deterministic way to start the GPU thread.
void FifoManager::Prepare(Core::System& system)
void FifoManager::Prepare()
{
m_event_sync_gpu = system.GetCoreTiming().RegisterEvent("SyncGPUCallback", SyncGPUCallback);
m_event_sync_gpu = m_system.GetCoreTiming().RegisterEvent("SyncGPUCallback", SyncGPUCallback);
m_syncing_suspended = true;
}
} // namespace Fifo

View file

@ -41,19 +41,19 @@ enum class SyncGPUReason
class FifoManager final
{
public:
FifoManager();
explicit FifoManager(Core::System& system);
FifoManager(const FifoManager& other) = delete;
FifoManager(FifoManager&& other) = delete;
FifoManager& operator=(const FifoManager& other) = delete;
FifoManager& operator=(FifoManager&& other) = delete;
~FifoManager();
void Init(Core::System& system);
void Init();
void Shutdown();
void Prepare(Core::System& system); // Must be called from the CPU thread.
void Prepare(); // Must be called from the CPU thread.
void DoState(PointerWrap& f);
void PauseAndLock(Core::System& system, bool doLock, bool unpauseOnUnlock);
void UpdateWantDeterminism(Core::System& system, bool want);
void PauseAndLock(bool do_lock, bool unpause_on_unlock);
void UpdateWantDeterminism(bool want);
bool UseDeterministicGPUThread() const { return m_use_deterministic_gpu_thread; }
bool UseSyncGPU() const { return m_config_sync_gpu; }
@ -62,25 +62,25 @@ public:
// In single core mode, this runs the GPU for a single slice.
// In dual core mode, this synchronizes with the GPU thread.
void SyncGPUForRegisterAccess(Core::System& system);
void SyncGPUForRegisterAccess();
void PushFifoAuxBuffer(const void* ptr, size_t size);
void* PopFifoAuxBuffer(size_t size);
void FlushGpu(Core::System& system);
void RunGpu(Core::System& system);
void FlushGpu();
void RunGpu();
void GpuMaySleep();
void RunGpuLoop(Core::System& system);
void ExitGpuLoop(Core::System& system);
void RunGpuLoop();
void ExitGpuLoop();
void EmulatorState(bool running);
void ResetVideoBuffer();
private:
void RefreshConfig();
void ReadDataFromFifo(Core::System& system, u32 readPtr);
void ReadDataFromFifoOnCPU(Core::System& system, u32 readPtr);
int RunGpuOnCpu(Core::System& system, int ticks);
int WaitForGpuThread(Core::System& system, int ticks);
void ReadDataFromFifo(u32 read_ptr);
void ReadDataFromFifoOnCPU(u32 read_ptr);
int RunGpuOnCpu(int ticks);
int WaitForGpuThread(int ticks);
static void SyncGPUCallback(Core::System& system, u64 ticks, s64 cyclesLate);
static constexpr u32 FIFO_SIZE = 2 * 1024 * 1024;
@ -127,6 +127,8 @@ private:
int m_config_sync_gpu_max_distance = 0;
int m_config_sync_gpu_min_distance = 0;
float m_config_sync_gpu_overclock = 0.0f;
Core::System& m_system;
};
bool AtBreakpoint(Core::System& system);

View file

@ -82,7 +82,7 @@ bool FrameDumper::CheckFrameDumpRenderTexture(u32 target_width, u32 target_heigh
m_frame_dump_render_texture.reset();
m_frame_dump_render_texture = g_gfx->CreateTexture(
TextureConfig(target_width, target_height, 1, 1, 1, AbstractTextureFormat::RGBA8,
AbstractTextureFlag_RenderTarget),
AbstractTextureFlag_RenderTarget, AbstractTextureType::Texture_2DArray),
"Frame dump render texture");
if (!m_frame_dump_render_texture)
{
@ -102,9 +102,10 @@ bool FrameDumper::CheckFrameDumpReadbackTexture(u32 target_width, u32 target_hei
return true;
rbtex.reset();
rbtex = g_gfx->CreateStagingTexture(
StagingTextureType::Readback,
TextureConfig(target_width, target_height, 1, 1, 1, AbstractTextureFormat::RGBA8, 0));
rbtex = g_gfx->CreateStagingTexture(StagingTextureType::Readback,
TextureConfig(target_width, target_height, 1, 1, 1,
AbstractTextureFormat::RGBA8, 0,
AbstractTextureType::Texture_2DArray));
if (!rbtex)
return false;

View file

@ -152,13 +152,15 @@ static u32 CalculateEFBLayers()
TextureConfig FramebufferManager::GetEFBColorTextureConfig(u32 width, u32 height)
{
return TextureConfig(width, height, 1, CalculateEFBLayers(), g_ActiveConfig.iMultisamples,
GetEFBColorFormat(), AbstractTextureFlag_RenderTarget);
GetEFBColorFormat(), AbstractTextureFlag_RenderTarget,
AbstractTextureType::Texture_2DArray);
}
TextureConfig FramebufferManager::GetEFBDepthTextureConfig(u32 width, u32 height)
{
return TextureConfig(width, height, 1, CalculateEFBLayers(), g_ActiveConfig.iMultisamples,
GetEFBDepthFormat(), AbstractTextureFlag_RenderTarget);
GetEFBDepthFormat(), AbstractTextureFlag_RenderTarget,
AbstractTextureType::Texture_2DArray);
}
FramebufferState FramebufferManager::GetEFBFramebufferState() const
@ -254,7 +256,8 @@ bool FramebufferManager::CreateEFBFramebuffer()
flags |= AbstractTextureFlag_RenderTarget;
m_efb_resolve_color_texture = g_gfx->CreateTexture(
TextureConfig(efb_color_texture_config.width, efb_color_texture_config.height, 1,
efb_color_texture_config.layers, 1, efb_color_texture_config.format, flags),
efb_color_texture_config.layers, 1, efb_color_texture_config.format, flags,
AbstractTextureType::Texture_2DArray),
"EFB color resolve texture");
if (!m_efb_resolve_color_texture)
return false;
@ -274,7 +277,7 @@ bool FramebufferManager::CreateEFBFramebuffer()
m_efb_depth_resolve_texture = g_gfx->CreateTexture(
TextureConfig(efb_depth_texture_config.width, efb_depth_texture_config.height, 1,
efb_depth_texture_config.layers, 1, GetEFBDepthCopyFormat(),
AbstractTextureFlag_RenderTarget),
AbstractTextureFlag_RenderTarget, AbstractTextureType::Texture_2DArray),
"EFB depth resolve texture");
if (!m_efb_depth_resolve_texture)
return false;
@ -695,7 +698,8 @@ bool FramebufferManager::CreateReadbackFramebuffer()
{
const TextureConfig color_config(IsUsingTiledEFBCache() ? m_efb_cache_tile_size : EFB_WIDTH,
IsUsingTiledEFBCache() ? m_efb_cache_tile_size : EFB_HEIGHT, 1,
1, 1, GetEFBColorFormat(), AbstractTextureFlag_RenderTarget);
1, 1, GetEFBColorFormat(), AbstractTextureFlag_RenderTarget,
AbstractTextureType::Texture_2DArray);
m_efb_color_cache.texture = g_gfx->CreateTexture(color_config, "EFB color cache");
if (!m_efb_color_cache.texture)
return false;
@ -717,7 +721,8 @@ bool FramebufferManager::CreateReadbackFramebuffer()
const TextureConfig depth_config(IsUsingTiledEFBCache() ? m_efb_cache_tile_size : EFB_WIDTH,
IsUsingTiledEFBCache() ? m_efb_cache_tile_size : EFB_HEIGHT, 1,
1, 1, GetEFBDepthCopyFormat(),
AbstractTextureFlag_RenderTarget);
AbstractTextureFlag_RenderTarget,
AbstractTextureType::Texture_2DArray);
m_efb_depth_cache.texture = g_gfx->CreateTexture(depth_config, "EFB depth cache");
if (!m_efb_depth_cache.texture)
return false;
@ -729,12 +734,14 @@ bool FramebufferManager::CreateReadbackFramebuffer()
}
// Staging texture use the full EFB dimensions, as this is the buffer for the whole cache.
m_efb_color_cache.readback_texture = g_gfx->CreateStagingTexture(
StagingTextureType::Mutable,
TextureConfig(EFB_WIDTH, EFB_HEIGHT, 1, 1, 1, GetEFBColorFormat(), 0));
m_efb_color_cache.readback_texture =
g_gfx->CreateStagingTexture(StagingTextureType::Mutable,
TextureConfig(EFB_WIDTH, EFB_HEIGHT, 1, 1, 1, GetEFBColorFormat(),
0, AbstractTextureType::Texture_2DArray));
m_efb_depth_cache.readback_texture = g_gfx->CreateStagingTexture(
StagingTextureType::Mutable,
TextureConfig(EFB_WIDTH, EFB_HEIGHT, 1, 1, 1, GetEFBDepthCopyFormat(), 0));
TextureConfig(EFB_WIDTH, EFB_HEIGHT, 1, 1, 1, GetEFBDepthCopyFormat(), 0,
AbstractTextureType::Texture_2DArray));
if (!m_efb_color_cache.readback_texture || !m_efb_depth_cache.readback_texture)
return false;
@ -1116,14 +1123,15 @@ void FramebufferManager::DoSaveState(PointerWrap& p)
AbstractTexture* depth_texture = ResolveEFBDepthTexture(m_efb_depth_texture->GetRect(), true);
// We don't want to save these as rendertarget textures, just the data itself when deserializing.
const TextureConfig color_texture_config(color_texture->GetWidth(), color_texture->GetHeight(),
color_texture->GetLevels(), color_texture->GetLayers(),
1, GetEFBColorFormat(), 0);
const TextureConfig color_texture_config(
color_texture->GetWidth(), color_texture->GetHeight(), color_texture->GetLevels(),
color_texture->GetLayers(), 1, GetEFBColorFormat(), 0, AbstractTextureType::Texture_2DArray);
g_texture_cache->SerializeTexture(color_texture, color_texture_config, p);
const TextureConfig depth_texture_config(depth_texture->GetWidth(), depth_texture->GetHeight(),
depth_texture->GetLevels(), depth_texture->GetLayers(),
1, GetEFBDepthCopyFormat(), 0);
1, GetEFBDepthCopyFormat(), 0,
AbstractTextureType::Texture_2DArray);
g_texture_cache->SerializeTexture(depth_texture, depth_texture_config, p);
}

View file

@ -19,7 +19,7 @@ namespace VideoCommon
// As pipelines encompass both shader UIDs and render states, changes to either of these should
// also increment the pipeline UID version. Incrementing the UID version will cause all UID
// caches to be invalidated.
constexpr u32 GX_PIPELINE_UID_VERSION = 7; // Last changed in PR 11859
constexpr u32 GX_PIPELINE_UID_VERSION = 8; // Last changed in PR 12185
struct GXPipelineUid
{

View file

@ -93,7 +93,7 @@ ShaderCode GenerateGeometryShaderCode(APIType api_type, const ShaderHostConfig&
// uniforms
if (api_type == APIType::OpenGL || api_type == APIType::Vulkan)
out.Write("UBO_BINDING(std140, 3) uniform GSBlock {{\n");
out.Write("UBO_BINDING(std140, 4) uniform GSBlock {{\n");
else
out.Write("cbuffer GSBlock {{\n");

View file

@ -393,21 +393,15 @@ void CustomPipelineAction::OnTextureCreate(GraphicsModActionData::TextureCreate*
return;
}
if (property.m_type == VideoCommon::MaterialProperty::Type::Type_TextureAsset)
if (auto* value = std::get_if<std::string>(&property.m_value))
{
if (property.m_value)
auto asset = loader.LoadGameTexture(*value, m_library);
if (asset)
{
if (auto* value = std::get_if<std::string>(&*property.m_value))
{
auto asset = loader.LoadGameTexture(*value, m_library);
if (asset)
{
const auto loaded_time = asset->GetLastLoadedTime();
game_assets.push_back(VideoCommon::CachedAsset<VideoCommon::GameTextureAsset>{
std::move(asset), loaded_time});
m_texture_code_names.push_back(property.m_code_name);
}
}
const auto loaded_time = asset->GetLastLoadedTime();
game_assets.push_back(
VideoCommon::CachedAsset<VideoCommon::GameTextureAsset>{std::move(asset), loaded_time});
m_texture_code_names.push_back(property.m_code_name);
}
}
}
@ -423,7 +417,7 @@ void CustomPipelineAction::OnTextureCreate(GraphicsModActionData::TextureCreate*
auto data = game_texture.m_asset->GetData();
if (data)
{
if (data->m_slices.empty() || data->m_slices[0].m_levels.empty())
if (data->m_texture.m_slices.empty() || data->m_texture.m_slices[0].m_levels.empty())
{
ERROR_LOG_FMT(
VIDEO,
@ -431,15 +425,16 @@ void CustomPipelineAction::OnTextureCreate(GraphicsModActionData::TextureCreate*
create->texture_name, game_texture.m_asset->GetAssetId());
m_valid = false;
}
else if (create->texture_width != data->m_slices[0].m_levels[0].width ||
create->texture_height != data->m_slices[0].m_levels[0].height)
else if (create->texture_width != data->m_texture.m_slices[0].m_levels[0].width ||
create->texture_height != data->m_texture.m_slices[0].m_levels[0].height)
{
ERROR_LOG_FMT(VIDEO,
"Custom pipeline for texture '{}' has asset '{}' that does not match "
"the width/height of the texture loaded. Texture {}x{} vs asset {}x{}",
create->texture_name, game_texture.m_asset->GetAssetId(),
create->texture_width, create->texture_height,
data->m_slices[0].m_levels[0].width, data->m_slices[0].m_levels[0].height);
data->m_texture.m_slices[0].m_levels[0].width,
data->m_texture.m_slices[0].m_levels[0].height);
m_valid = false;
}
}

View file

@ -95,8 +95,8 @@ void CustomShaderCache::AsyncCreatePipeline(const VideoCommon::GXPipelineUid& ui
PipelineWorkItem(CustomShaderCache* shader_cache, const VideoCommon::GXPipelineUid& uid,
const CustomShaderInstance& custom_shaders, PipelineIterator iterator,
const AbstractPipelineConfig& pipeline_config)
: m_shader_cache(shader_cache), m_uid(uid), m_iterator(iterator),
m_custom_shaders(custom_shaders), m_config(pipeline_config)
: m_shader_cache(shader_cache), m_uid(uid), m_iterator(iterator), m_config(pipeline_config),
m_custom_shaders(custom_shaders)
{
SetStagesReady();
}
@ -179,8 +179,8 @@ void CustomShaderCache::AsyncCreatePipeline(const VideoCommon::GXUberPipelineUid
PipelineWorkItem(CustomShaderCache* shader_cache, const VideoCommon::GXUberPipelineUid& uid,
const CustomShaderInstance& custom_shaders, UberPipelineIterator iterator,
const AbstractPipelineConfig& pipeline_config)
: m_shader_cache(shader_cache), m_uid(uid), m_iterator(iterator),
m_custom_shaders(custom_shaders), m_config(pipeline_config)
: m_shader_cache(shader_cache), m_uid(uid), m_iterator(iterator), m_config(pipeline_config),
m_custom_shaders(custom_shaders)
{
SetStagesReady();
}

View file

@ -5,6 +5,7 @@
#include <array>
#include <optional>
#include <span>
#include <string_view>
#include <vector>
@ -20,6 +21,7 @@ struct DrawStarted
u32 texture_unit;
bool* skip;
std::optional<CustomPixelShader>* custom_pixel_shader;
std::span<u8>* material_uniform_buffer;
};
struct EFB

View file

@ -3,6 +3,9 @@
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.h"
#include "Common/Logging/Log.h"
#include "VideoCommon/Constants.h"
#include "VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.h"
#include "VideoCommon/GraphicsModSystem/Runtime/Actions/MoveAction.h"
#include "VideoCommon/GraphicsModSystem/Runtime/Actions/PrintAction.h"
@ -32,7 +35,16 @@ std::unique_ptr<GraphicsModAction> Create(std::string_view name, const picojson:
}
else if (name == "custom_pipeline")
{
#ifdef ANDROID
// Custom shaders currently need more than 8 pixel samplers
// to be used with textures, rather than make things complicated
// just disable the feature for Android which has issues
ERROR_LOG_FMT(VIDEO, "Android needs more than 8 pixel samplers to function, {} provided",
VideoCommon::MAX_PIXEL_SHADER_SAMPLERS);
return nullptr;
#else
return CustomPipelineAction::Create(json_data, std::move(library));
#endif
}
return nullptr;

View file

@ -130,8 +130,8 @@ void HiresTexture::Update()
{
// Since this is just a texture (single file) the mapper doesn't really matter
// just provide a string
s_file_library->SetAssetIDMapData(
filename, std::map<std::string, std::filesystem::path>{{"", StringToPath(path)}});
s_file_library->SetAssetIDMapData(filename, std::map<std::string, std::filesystem::path>{
{"texture", StringToPath(path)}});
if (g_ActiveConfig.bCacheHiresTextures)
{

View file

@ -214,43 +214,44 @@ void GenerateCustomLightingHeaderDetails(ShaderCode* out, u32 enablelighting, u3
out->Write("\tint light_count;\n");
}
static void GenerateLighting(ShaderCode* out, const LightingUidData& uid_data, int index,
int litchan_index, u32 channel_index, u32 custom_light_index,
bool alpha)
{
const auto attnfunc =
static_cast<AttenuationFunc>((uid_data.attnfunc >> (2 * litchan_index)) & 0x3);
const std::string_view light_type = alpha ? "alpha" : "color";
const std::string name = fmt::format("lights_chan{}_{}", channel_index, light_type);
out->Write("\t{{\n");
out->Write("\t\tcustom_data.{}[{}].direction = " LIGHT_DIR ".xyz;\n", name, custom_light_index,
LIGHT_DIR_PARAMS(index));
out->Write("\t\tcustom_data.{}[{}].position = " LIGHT_POS ".xyz;\n", name, custom_light_index,
LIGHT_POS_PARAMS(index));
out->Write("\t\tcustom_data.{}[{}].cosatt = " LIGHT_COSATT ";\n", name, custom_light_index,
LIGHT_COSATT_PARAMS(index));
out->Write("\t\tcustom_data.{}[{}].distatt = " LIGHT_DISTATT ";\n", name, custom_light_index,
LIGHT_DISTATT_PARAMS(index));
out->Write("\t\tcustom_data.{}[{}].attenuation_type = {};\n", name, custom_light_index,
static_cast<u32>(attnfunc));
if (alpha)
{
out->Write("\t\tcustom_data.{}[{}].color = float3(" LIGHT_COL
") / float3(255.0, 255.0, 255.0);\n",
name, custom_light_index, LIGHT_COL_PARAMS(index, alpha ? "a" : "rgb"));
}
else
{
out->Write("\t\tcustom_data.{}[{}].color = " LIGHT_COL " / float3(255.0, 255.0, 255.0);\n",
name, custom_light_index, LIGHT_COL_PARAMS(index, alpha ? "a" : "rgb"));
}
out->Write("\t}}\n");
}
void GenerateCustomLightingImplementation(ShaderCode* out, const LightingUidData& uid_data,
std::string_view in_color_name)
{
auto generate_lighting = [](ShaderCode* out, const LightingUidData& uid_data, int index,
int litchan_index, u32 channel_index, u32 custom_light_index,
bool alpha) {
const auto attnfunc =
static_cast<AttenuationFunc>((uid_data.attnfunc >> (2 * litchan_index)) & 0x3);
const std::string_view light_type = alpha ? "alpha" : "color";
const std::string name = fmt::format("lights_chan{}_{}", channel_index, light_type);
out->Write("\t{{\n");
out->Write("\t\tcustom_data.{}[{}].direction = " LIGHT_DIR ".xyz;\n", name, custom_light_index,
LIGHT_DIR_PARAMS(index));
out->Write("\t\tcustom_data.{}[{}].position = " LIGHT_POS ".xyz;\n", name, custom_light_index,
LIGHT_POS_PARAMS(index));
out->Write("\t\tcustom_data.{}[{}].cosatt = " LIGHT_COSATT ";\n", name, custom_light_index,
LIGHT_COSATT_PARAMS(index));
out->Write("\t\tcustom_data.{}[{}].distatt = " LIGHT_DISTATT ";\n", name, custom_light_index,
LIGHT_DISTATT_PARAMS(index));
out->Write("\t\tcustom_data.{}[{}].attenuation_type = {};\n", name, custom_light_index,
static_cast<u32>(attnfunc));
if (alpha)
{
out->Write("\t\tcustom_data.{}[{}].color = float3(" LIGHT_COL
") / float3(255.0, 255.0, 255.0);\n",
name, custom_light_index, LIGHT_COL_PARAMS(index, alpha ? "a" : "rgb"));
}
else
{
out->Write("\t\tcustom_data.{}[{}].color = " LIGHT_COL " / float3(255.0, 255.0, 255.0);\n",
name, custom_light_index, LIGHT_COL_PARAMS(index, alpha ? "a" : "rgb"));
}
out->Write("\t}}\n");
};
for (u32 i = 0; i < 8; i++)
{
for (u32 channel_index = 0; channel_index < NUM_XF_COLOR_CHANNELS; channel_index++)
@ -330,7 +331,7 @@ void GenerateCustomLightingImplementation(ShaderCode* out, const LightingUidData
{
if ((uid_data.light_mask & (1 << (i + 8 * j))) != 0)
{
generate_lighting(out, uid_data, i, j, j, light_count, false);
GenerateLighting(out, uid_data, i, j, j, light_count, false);
light_count++;
}
}
@ -344,7 +345,7 @@ void GenerateCustomLightingImplementation(ShaderCode* out, const LightingUidData
{
if ((uid_data.light_mask & (1 << (i + 8 * (j + 2)))) != 0)
{
generate_lighting(out, uid_data, i, j + 2, j, light_count, true);
GenerateLighting(out, uid_data, i, j + 2, j, light_count, true);
light_count++;
}
}

View file

@ -18,6 +18,10 @@
#include "Core/Config/MainSettings.h"
#include "VideoCommon/AbstractGfx.h"
#include "VideoCommon/AbstractTexture.h"
#include "VideoCommon/TextureConfig.h"
namespace OSD
{
constexpr float LEFT_MARGIN = 10.0f; // Pixels to the left of OSD messages.
@ -32,8 +36,8 @@ static std::atomic<int> s_obscured_pixels_top = 0;
struct Message
{
Message() = default;
Message(std::string text_, u32 duration_, u32 color_)
: text(std::move(text_)), duration(duration_), color(color_)
Message(std::string text_, u32 duration_, u32 color_, std::unique_ptr<Icon> icon_ = nullptr)
: text(std::move(text_)), duration(duration_), color(color_), icon(std::move(icon_))
{
timer.Start();
}
@ -42,7 +46,10 @@ struct Message
Common::Timer timer;
u32 duration = 0;
bool ever_drawn = false;
bool should_discard = false;
u32 color = 0;
std::unique_ptr<Icon> icon;
std::unique_ptr<AbstractTexture> texture;
};
static std::multimap<MessageType, Message> s_messages;
static std::mutex s_messages_mutex;
@ -77,6 +84,34 @@ static float DrawMessage(int index, Message& msg, const ImVec2& position, int ti
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoNav |
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing))
{
if (msg.icon)
{
if (!msg.texture)
{
const u32 width = msg.icon->width;
const u32 height = msg.icon->height;
TextureConfig tex_config(width, height, 1, 1, 1, AbstractTextureFormat::RGBA8, 0,
AbstractTextureType::Texture_2DArray);
msg.texture = g_gfx->CreateTexture(tex_config);
if (msg.texture)
{
msg.texture->Load(0, width, height, width, msg.icon->rgba_data.data(),
sizeof(u32) * width * height);
}
else
{
// don't try again next time
msg.icon.reset();
}
}
if (msg.texture)
{
ImGui::Image(msg.texture.get(), ImVec2(static_cast<float>(msg.icon->width),
static_cast<float>(msg.icon->height)));
}
}
// Use %s in case message contains %.
ImGui::TextColored(ARGBToImVec4(msg.color), "%s", msg.text.c_str());
window_height =
@ -91,17 +126,25 @@ static float DrawMessage(int index, Message& msg, const ImVec2& position, int ti
return window_height;
}
void AddTypedMessage(MessageType type, std::string message, u32 ms, u32 argb)
void AddTypedMessage(MessageType type, std::string message, u32 ms, u32 argb,
std::unique_ptr<Icon> icon)
{
std::lock_guard lock{s_messages_mutex};
s_messages.erase(type);
s_messages.emplace(type, Message(std::move(message), ms, argb));
// A message may hold a reference to a texture that can only be destroyed on the video thread, so
// only mark the old typed message (if any) for removal. It will be discarded on the next call to
// DrawMessages().
auto range = s_messages.equal_range(type);
for (auto it = range.first; it != range.second; ++it)
it->second.should_discard = true;
s_messages.emplace(type, Message(std::move(message), ms, argb, std::move(icon)));
}
void AddMessage(std::string message, u32 ms, u32 argb)
void AddMessage(std::string message, u32 ms, u32 argb, std::unique_ptr<Icon> icon)
{
std::lock_guard lock{s_messages_mutex};
s_messages.emplace(MessageType::Typeless, Message(std::move(message), ms, argb));
s_messages.emplace(MessageType::Typeless, Message(std::move(message), ms, argb, std::move(icon)));
}
void DrawMessages()
@ -117,6 +160,12 @@ void DrawMessages()
for (auto it = s_messages.begin(); it != s_messages.end();)
{
Message& msg = it->second;
if (msg.should_discard)
{
it = s_messages.erase(it);
continue;
}
const s64 time_left = msg.TimeRemaining();
// Make sure we draw them at least once if they were printed with 0ms,

View file

@ -4,7 +4,9 @@
#pragma once
#include <functional>
#include <memory>
#include <string>
#include <vector>
#include "Common/CommonTypes.h"
@ -35,10 +37,18 @@ constexpr u32 NORMAL = 5000;
constexpr u32 VERY_LONG = 10000;
}; // namespace Duration
struct Icon
{
std::vector<u8> rgba_data;
u32 width = 0;
u32 height = 0;
}; // struct Icon
// On-screen message display (colored yellow by default)
void AddMessage(std::string message, u32 ms = Duration::SHORT, u32 argb = Color::YELLOW);
void AddMessage(std::string message, u32 ms = Duration::SHORT, u32 argb = Color::YELLOW,
std::unique_ptr<Icon> icon = nullptr);
void AddTypedMessage(MessageType type, std::string message, u32 ms = Duration::SHORT,
u32 argb = Color::YELLOW);
u32 argb = Color::YELLOW, std::unique_ptr<Icon> icon = nullptr);
// Draw the current messages on the screen. Only call once per frame.
void DrawMessages();

View file

@ -7,6 +7,7 @@
#include "Common/Profiler.h"
#include "Common/Timer.h"
#include "Core/AchievementManager.h"
#include "Core/Config/MainSettings.h"
#include "Core/Config/NetplaySettings.h"
#include "Core/Movie.h"
@ -76,7 +77,8 @@ bool OnScreenUI::Initialize(u32 width, u32 height, float scale)
io.Fonts->GetTexDataAsRGBA32(&font_tex_pixels, &font_tex_width, &font_tex_height);
TextureConfig font_tex_config(font_tex_width, font_tex_height, 1, 1, 1,
AbstractTextureFormat::RGBA8, 0);
AbstractTextureFormat::RGBA8, 0,
AbstractTextureType::Texture_2DArray);
std::unique_ptr<AbstractTexture> font_tex =
g_gfx->CreateTexture(font_tex_config, "ImGui font texture");
if (!font_tex)
@ -326,6 +328,67 @@ void OnScreenUI::DrawDebugText()
ImGui::TextUnformatted(profile_output.c_str());
}
#ifdef USE_RETRO_ACHIEVEMENTS
void OnScreenUI::DrawChallenges()
{
std::lock_guard lg{AchievementManager::GetInstance().GetLock()};
const auto& challenge_icons = AchievementManager::GetInstance().GetChallengeIcons();
if (challenge_icons.empty())
return;
const std::string window_name = "Challenges";
u32 sum_of_icon_heights = 0;
u32 max_icon_width = 0;
for (const auto& [name, icon] : challenge_icons)
{
// These *should* all be the same square size but you never know.
if (icon->width > max_icon_width)
max_icon_width = icon->width;
sum_of_icon_heights += icon->height;
}
ImGui::SetNextWindowPos(
ImVec2(ImGui::GetIO().DisplaySize.x - 20.f * m_backbuffer_scale - max_icon_width,
ImGui::GetIO().DisplaySize.y - 20.f * m_backbuffer_scale - sum_of_icon_heights));
ImGui::SetNextWindowSize(ImVec2(0.0f, 0.0f));
if (ImGui::Begin(window_name.c_str(), nullptr,
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoInputs |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings |
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoNav |
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing))
{
for (const auto& [name, icon] : challenge_icons)
{
if (m_challenge_texture_map.find(name) != m_challenge_texture_map.end())
continue;
const u32 width = icon->width;
const u32 height = icon->height;
TextureConfig tex_config(width, height, 1, 1, 1, AbstractTextureFormat::RGBA8, 0,
AbstractTextureType::Texture_2DArray);
auto res = m_challenge_texture_map.insert_or_assign(name, g_gfx->CreateTexture(tex_config));
res.first->second->Load(0, width, height, width, icon->rgba_data.data(),
sizeof(u32) * width * height);
}
for (auto& [name, texture] : m_challenge_texture_map)
{
auto icon_itr = challenge_icons.find(name);
if (icon_itr == challenge_icons.end())
{
m_challenge_texture_map.erase(name);
continue;
}
if (texture)
{
ImGui::Image(texture.get(), ImVec2(static_cast<float>(icon_itr->second->width),
static_cast<float>(icon_itr->second->height)));
}
}
}
ImGui::End();
}
#endif // USE_RETRO_ACHIEVEMENTS
void OnScreenUI::Finalize()
{
auto lock = GetImGuiLock();
@ -333,6 +396,9 @@ void OnScreenUI::Finalize()
g_perf_metrics.DrawImGuiStats(m_backbuffer_scale);
DrawDebugText();
OSD::DrawMessages();
#ifdef USE_RETRO_ACHIEVEMENTS
DrawChallenges();
#endif // USE_RETRO_ACHIEVEMENTS
ImGui::Render();
}

View file

@ -61,6 +61,9 @@ public:
private:
void DrawDebugText();
#ifdef USE_RETRO_ACHIEVEMENTS
void DrawChallenges();
#endif // USE_RETRO_ACHIEVEMENTS
// ImGui resources.
std::unique_ptr<NativeVertexFormat> m_imgui_vertex_format;
@ -74,6 +77,10 @@ private:
u32 m_backbuffer_height = 1;
float m_backbuffer_scale = 1.0;
#ifdef USE_RETRO_ACHIEVEMENTS
std::map<std::string, std::unique_ptr<AbstractTexture>, std::less<>> m_challenge_texture_map;
#endif // USE_RETRO_ACHIEVEMENTS
bool m_ready = false;
};
} // namespace VideoCommon

View file

@ -27,8 +27,8 @@
#include "VideoCommon/Statistics.h"
#include "VideoCommon/VertexLoaderBase.h"
#include "VideoCommon/VertexLoaderManager.h"
#include "VideoCommon/VertexShaderManager.h"
#include "VideoCommon/XFMemory.h"
#include "VideoCommon/XFStateManager.h"
#include "VideoCommon/XFStructs.h"
namespace OpcodeDecoder
@ -60,13 +60,13 @@ public:
{
VertexLoaderManager::g_needs_cp_xf_consistency_check = true;
auto& system = Core::System::GetInstance();
system.GetVertexShaderManager().SetTexMatrixChangedA(value);
system.GetXFStateManager().SetTexMatrixChangedA(value);
}
else if (sub_command == MATINDEX_B)
{
VertexLoaderManager::g_needs_cp_xf_consistency_check = true;
auto& system = Core::System::GetInstance();
system.GetVertexShaderManager().SetTexMatrixChangedB(value);
system.GetXFStateManager().SetTexMatrixChangedB(value);
}
else if (sub_command == VCD_LO || sub_command == VCD_HI)
{
@ -220,7 +220,7 @@ public:
else
{
auto& system = Core::System::GetInstance();
system.GetCommandProcessor().HandleUnknownOpcode(system, opcode, data, is_preprocess);
system.GetCommandProcessor().HandleUnknownOpcode(opcode, data, is_preprocess);
m_cycles += 1;
}
}

View file

@ -159,7 +159,7 @@ static DOLPHIN_FORCE_INLINE u32 RunCommand(const u8* data, u32 available, T& cal
const u16 base_address = cmd2 & 0xffff;
const u16 stream_size_temp = cmd2 >> 16;
ASSERT(stream_size_temp < 16);
ASSERT_MSG(VIDEO, stream_size_temp < 16, "cmd2 = 0x{:08X}", cmd2);
const u8 stream_size = (stream_size_temp & 0xf) + 1;
if (available < u32(5 + stream_size * 4))

View file

@ -30,6 +30,10 @@ enum
INT_CAUSE_PE_FINISH = 0x400, // GP Finished
};
PixelEngineManager::PixelEngineManager(Core::System& system) : m_system{system}
{
}
void PixelEngineManager::DoState(PointerWrap& p)
{
p.Do(m_z_conf);
@ -49,7 +53,7 @@ void PixelEngineManager::DoState(PointerWrap& p)
p.Do(m_signal_finish_interrupt);
}
void PixelEngineManager::Init(Core::System& system)
void PixelEngineManager::Init()
{
m_control.hex = 0;
m_z_conf.hex = 0;
@ -68,7 +72,7 @@ void PixelEngineManager::Init(Core::System& system)
m_signal_finish_interrupt = false;
m_event_type_set_token_finish =
system.GetCoreTiming().RegisterEvent("SetTokenFinish", SetTokenFinish_OnMainThread_Static);
m_system.GetCoreTiming().RegisterEvent("SetTokenFinish", SetTokenFinish_OnMainThread_Static);
}
void PixelEngineManager::RegisterMMIO(MMIO::Mapping* mmio, u32 base)
@ -156,8 +160,7 @@ void PixelEngineManager::RegisterMMIO(MMIO::Mapping* mmio, u32 base)
void PixelEngineManager::UpdateInterrupts()
{
auto& system = Core::System::GetInstance();
auto& processor_interface = system.GetProcessorInterface();
auto& processor_interface = m_system.GetProcessorInterface();
// check if there is a token-interrupt
processor_interface.SetInterrupt(INT_CAUSE_PE_TOKEN,
@ -171,13 +174,12 @@ void PixelEngineManager::UpdateInterrupts()
void PixelEngineManager::SetTokenFinish_OnMainThread_Static(Core::System& system, u64 userdata,
s64 cycles_late)
{
system.GetPixelEngine().SetTokenFinish_OnMainThread(system, userdata, cycles_late);
system.GetPixelEngine().SetTokenFinish_OnMainThread(userdata, cycles_late);
}
void PixelEngineManager::SetTokenFinish_OnMainThread(Core::System& system, u64 userdata,
s64 cycles_late)
void PixelEngineManager::SetTokenFinish_OnMainThread(u64 userdata, s64 cycles_late)
{
std::unique_lock<std::mutex> lk(m_token_finish_mutex);
std::unique_lock lk(m_token_finish_mutex);
m_event_raised = false;
m_token = m_token_pending;
@ -202,7 +204,7 @@ void PixelEngineManager::SetTokenFinish_OnMainThread(Core::System& system, u64 u
// Raise the event handler above on the CPU thread.
// m_token_finish_mutex must be locked.
// THIS IS EXECUTED FROM VIDEO THREAD
void PixelEngineManager::RaiseEvent(Core::System& system, int cycles_into_future)
void PixelEngineManager::RaiseEvent(int cycles_into_future)
{
if (m_event_raised)
return;
@ -211,7 +213,7 @@ void PixelEngineManager::RaiseEvent(Core::System& system, int cycles_into_future
CoreTiming::FromThread from = CoreTiming::FromThread::NON_CPU;
s64 cycles = 0; // we don't care about timings for dual core mode.
if (!system.IsDualCoreMode() || system.GetFifo().UseDeterministicGPUThread())
if (!m_system.IsDualCoreMode() || m_system.GetFifo().UseDeterministicGPUThread())
{
from = CoreTiming::FromThread::CPU;
@ -219,35 +221,34 @@ void PixelEngineManager::RaiseEvent(Core::System& system, int cycles_into_future
// games time to setup any interrupt state
cycles = std::max(500, cycles_into_future);
}
system.GetCoreTiming().ScheduleEvent(cycles, m_event_type_set_token_finish, 0, from);
m_system.GetCoreTiming().ScheduleEvent(cycles, m_event_type_set_token_finish, 0, from);
}
// SetToken
// THIS IS EXECUTED FROM VIDEO THREAD
void PixelEngineManager::SetToken(Core::System& system, const u16 token, const bool interrupt,
int cycles_into_future)
void PixelEngineManager::SetToken(const u16 token, const bool interrupt, int cycles_into_future)
{
DEBUG_LOG_FMT(PIXELENGINE, "VIDEO Backend raises INT_CAUSE_PE_TOKEN (btw, token: {:04x})", token);
std::lock_guard<std::mutex> lk(m_token_finish_mutex);
std::lock_guard lk(m_token_finish_mutex);
m_token_pending = token;
m_token_interrupt_pending |= interrupt;
RaiseEvent(system, cycles_into_future);
RaiseEvent(cycles_into_future);
}
// SetFinish
// THIS IS EXECUTED FROM VIDEO THREAD (BPStructs.cpp) when a new frame has been drawn
void PixelEngineManager::SetFinish(Core::System& system, int cycles_into_future)
void PixelEngineManager::SetFinish(int cycles_into_future)
{
DEBUG_LOG_FMT(PIXELENGINE, "VIDEO Set Finish");
std::lock_guard<std::mutex> lk(m_token_finish_mutex);
std::lock_guard lk(m_token_finish_mutex);
m_finish_interrupt_pending |= true;
RaiseEvent(system, cycles_into_future);
RaiseEvent(cycles_into_future);
}
} // namespace PixelEngine

View file

@ -179,20 +179,22 @@ union UPECtrlReg
class PixelEngineManager
{
public:
void Init(Core::System& system);
explicit PixelEngineManager(Core::System& system);
void Init();
void DoState(PointerWrap& p);
void RegisterMMIO(MMIO::Mapping* mmio, u32 base);
// gfx backend support
void SetToken(Core::System& system, const u16 token, const bool interrupt, int cycle_delay);
void SetFinish(Core::System& system, int cycle_delay);
void SetToken(const u16 token, const bool interrupt, int cycle_delay);
void SetFinish(int cycle_delay);
AlphaReadMode GetAlphaReadMode() const { return m_alpha_read.read_mode; }
private:
void RaiseEvent(Core::System& system, int cycles_into_future);
void RaiseEvent(int cycles_into_future);
void UpdateInterrupts();
void SetTokenFinish_OnMainThread(Core::System& system, u64 userdata, s64 cycles_late);
void SetTokenFinish_OnMainThread(u64 userdata, s64 cycles_late);
static void SetTokenFinish_OnMainThread_Static(Core::System& system, u64 userdata,
s64 cycles_late);
@ -216,6 +218,8 @@ private:
bool m_signal_finish_interrupt = false;
CoreTiming::EventType* m_event_type_set_token_finish = nullptr;
Core::System& m_system;
};
} // namespace PixelEngine

View file

@ -345,7 +345,8 @@ void ClearUnusedPixelShaderUidBits(APIType api_type, const ShaderHostConfig& hos
}
void WritePixelShaderCommonHeader(ShaderCode& out, APIType api_type,
const ShaderHostConfig& host_config, bool bounding_box)
const ShaderHostConfig& host_config, bool bounding_box,
const CustomPixelShaderContents& custom_details)
{
// dot product for integer vectors
out.Write("int idot(int3 x, int3 y)\n"
@ -426,6 +427,14 @@ void WritePixelShaderCommonHeader(ShaderCode& out, APIType api_type,
out.Write("}};\n");
}
if (!custom_details.shaders.empty() &&
!custom_details.shaders.back().material_uniform_block.empty())
{
out.Write("UBO_BINDING(std140, 3) uniform CustomShaderBlock {{\n");
out.Write("{}", custom_details.shaders.back().material_uniform_block);
out.Write("}} custom_uniforms;\n");
}
if (bounding_box)
{
out.Write("SSBO_BINDING(0) coherent buffer BBox {{\n"
@ -907,7 +916,7 @@ ShaderCode GeneratePixelShaderCode(APIType api_type, const ShaderHostConfig& hos
// Stuff that is shared between ubershaders and pixelgen.
WriteBitfieldExtractHeader(out, api_type, host_config);
WritePixelShaderCommonHeader(out, api_type, host_config, uid_data->bounding_box);
WritePixelShaderCommonHeader(out, api_type, host_config, uid_data->bounding_box, custom_details);
// Custom shader details
WriteCustomShaderStructDef(&out, uid_data->genMode_numtexgens);

View file

@ -165,7 +165,8 @@ ShaderCode GeneratePixelShaderCode(APIType api_type, const ShaderHostConfig& hos
const pixel_shader_uid_data* uid_data,
const CustomPixelShaderContents& custom_details);
void WritePixelShaderCommonHeader(ShaderCode& out, APIType api_type,
const ShaderHostConfig& host_config, bool bounding_box);
const ShaderHostConfig& host_config, bool bounding_box,
const CustomPixelShaderContents& custom_details);
void ClearUnusedPixelShaderUidBits(APIType api_type, const ShaderHostConfig& host_config,
PixelShaderUid* uid);
PixelShaderUid GetPixelShaderUid();

View file

@ -3,6 +3,8 @@
#pragma once
#include <span>
#include "Common/CommonTypes.h"
#include "VideoCommon/ConstantManager.h"
@ -52,6 +54,10 @@ public:
PixelShaderConstants constants{};
bool dirty = false;
// Constants for custom shaders
std::span<u8> custom_constants;
bool custom_constants_dirty = false;
private:
bool m_fog_range_adjusted_changed = false;
bool m_viewport_changed = false;

View file

@ -39,8 +39,8 @@ static const char s_default_pixel_shader_name[] = "default_pre_post_process";
// RGBA16F should have enough quality even if we store colors in gamma space on it.
static const AbstractTextureFormat s_intermediary_buffer_format = AbstractTextureFormat::RGBA16F;
bool LoadShaderFromFile(const std::string& shader, const std::string& sub_dir,
std::string& out_code)
static bool LoadShaderFromFile(const std::string& shader, const std::string& sub_dir,
std::string& out_code)
{
std::string path = File::GetUserPath(D_SHADERS_IDX) + sub_dir + shader + ".glsl";
@ -530,7 +530,8 @@ void PostProcessing::BlitFromTexture(const MathUtil::Rectangle<int>& dst,
{
const TextureConfig intermediary_color_texture_config(
target_width, target_height, 1, target_layers, src_tex->GetSamples(),
s_intermediary_buffer_format, AbstractTextureFlag_RenderTarget);
s_intermediary_buffer_format, AbstractTextureFlag_RenderTarget,
AbstractTextureType::Texture_2DArray);
m_intermediary_color_texture = g_gfx->CreateTexture(intermediary_color_texture_config,
"Intermediary post process texture");
@ -769,7 +770,7 @@ std::string PostProcessing::GetFooter() const
return {};
}
std::string GetVertexShaderBody()
static std::string GetVertexShaderBody()
{
std::ostringstream ss;
if (g_ActiveConfig.backend_info.bSupportsGeometryShaders)
@ -1002,7 +1003,7 @@ bool PostProcessing::CompilePixelShader()
return true;
}
bool UseGeometryShaderForPostProcess(bool is_intermediary_buffer)
static bool UseGeometryShaderForPostProcess(bool is_intermediary_buffer)
{
// We only return true on stereo modes that need to copy
// both source texture layers into the target texture layers.

View file

@ -4,6 +4,7 @@
#include "VideoCommon/Present.h"
#include "Common/ChunkFile.h"
#include "Core/Config/GraphicsSettings.h"
#include "Core/HW/VideoInterface.h"
#include "Core/Host.h"
#include "Core/System.h"
@ -13,6 +14,7 @@
#include "Present.h"
#include "VideoCommon/AbstractGfx.h"
#include "VideoCommon/FrameDumper.h"
#include "VideoCommon/FramebufferManager.h"
#include "VideoCommon/OnScreenUI.h"
#include "VideoCommon/PostProcessing.h"
#include "VideoCommon/Statistics.h"
@ -28,9 +30,10 @@ static constexpr int VIDEO_ENCODER_LCM = 4;
namespace VideoCommon
{
static float AspectToWidescreen(float aspect)
// Stretches the native/internal analog resolution aspect ratio from ~4:3 to ~16:9
static float SourceAspectRatioToWidescreen(float source_aspect)
{
return aspect * ((16.0f / 9.0f) / (4.0f / 3.0f));
return source_aspect * ((16.0f / 9.0f) / (4.0f / 3.0f));
}
static std::tuple<int, int> FindClosestIntegerResolution(float width, float height,
@ -201,20 +204,46 @@ void Presenter::ProcessFrameDumping(u64 ticks) const
void Presenter::SetBackbuffer(int backbuffer_width, int backbuffer_height)
{
const bool is_first = m_backbuffer_width == 0 && m_backbuffer_height == 0;
const bool size_changed =
(m_backbuffer_width != backbuffer_width || m_backbuffer_height != backbuffer_height);
m_backbuffer_width = backbuffer_width;
m_backbuffer_height = backbuffer_height;
UpdateDrawRectangle();
OnBackbufferSet(size_changed, is_first);
}
void Presenter::SetBackbuffer(SurfaceInfo info)
{
const bool is_first = m_backbuffer_width == 0 && m_backbuffer_height == 0;
const bool size_changed =
(m_backbuffer_width != (int)info.width || m_backbuffer_height != (int)info.height);
m_backbuffer_width = info.width;
m_backbuffer_height = info.height;
m_backbuffer_scale = info.scale;
m_backbuffer_format = info.format;
if (m_onscreen_ui)
m_onscreen_ui->SetScale(info.scale);
OnBackbufferSet(size_changed, is_first);
}
void Presenter::OnBackbufferSet(bool size_changed, bool is_first_set)
{
UpdateDrawRectangle();
// Automatically update the resolution scale if the window size changed,
// or if the game XFB resolution changed.
if (size_changed && !is_first_set && g_ActiveConfig.iEFBScale == EFB_SCALE_AUTO_INTEGRAL &&
m_auto_resolution_scale != AutoIntegralScale())
{
g_framebuffer_manager->RecreateEFBFramebuffer();
}
if (size_changed || is_first_set)
{
m_auto_resolution_scale = AutoIntegralScale();
}
}
void Presenter::ConfigChanged(u32 changed_bits)
@ -292,15 +321,22 @@ float Presenter::CalculateDrawAspectRatio(bool allow_stretch) const
return (static_cast<float>(m_backbuffer_width) / static_cast<float>(m_backbuffer_height));
auto& vi = Core::System::GetInstance().GetVideoInterface();
const float aspect_ratio = vi.GetAspectRatio();
const float source_aspect_ratio = vi.GetAspectRatio();
if (aspect_mode == AspectMode::AnalogWide ||
// This will scale up the source ~4:3 resolution to its equivalent ~16:9 resolution
if (aspect_mode == AspectMode::ForceWide ||
(aspect_mode == AspectMode::Auto && g_widescreen->IsGameWidescreen()))
{
return AspectToWidescreen(aspect_ratio);
return SourceAspectRatioToWidescreen(source_aspect_ratio);
}
// For the "custom" mode, we force the exact target aspect ratio, without
// acknowleding the difference between the source aspect ratio and 4:3.
else if (aspect_mode == AspectMode::Custom)
{
return g_ActiveConfig.GetCustomAspectRatio();
}
return aspect_ratio;
return source_aspect_ratio;
}
void Presenter::AdjustRectanglesToFitBounds(MathUtil::Rectangle<int>* target_rect,
@ -365,45 +401,35 @@ void* Presenter::GetNewSurfaceHandle()
u32 Presenter::AutoIntegralScale() const
{
const float efb_aspect_ratio = static_cast<float>(EFB_WIDTH) / EFB_HEIGHT;
const float target_aspect_ratio =
static_cast<float>(m_target_rectangle.GetWidth()) / m_target_rectangle.GetHeight();
u32 target_width;
u32 target_height;
// Instead of using the entire window (back buffer) resolution,
// find the portion of it that will actually contain the EFB output,
// and ignore the portion that will likely have black bars.
if (target_aspect_ratio >= efb_aspect_ratio)
{
target_height = m_target_rectangle.GetHeight();
target_width = static_cast<u32>(
std::round((static_cast<float>(m_target_rectangle.GetWidth()) / target_aspect_ratio) *
efb_aspect_ratio));
}
// Take the source resolution (XFB) and stretch it on the target aspect ratio.
// If the target resolution is larger (on either x or y), we scale the source
// by a integer multiplier until it won't have to be scaled up anymore.
u32 source_width = m_last_xfb_width;
u32 source_height = m_last_xfb_height;
const u32 target_width = m_target_rectangle.GetWidth();
const u32 target_height = m_target_rectangle.GetHeight();
const float source_aspect_ratio = (float)source_width / source_height;
const float target_aspect_ratio = (float)target_width / target_height;
if (source_aspect_ratio >= target_aspect_ratio)
source_width = std::round(source_height * target_aspect_ratio);
else
{
target_width = m_target_rectangle.GetWidth();
target_height = static_cast<u32>(
std::round((static_cast<float>(m_target_rectangle.GetHeight()) * target_aspect_ratio) /
efb_aspect_ratio));
}
// Calculate a scale based on the adjusted window size
u32 width = EFB_WIDTH * target_width / m_last_xfb_width;
u32 height = EFB_HEIGHT * target_height / m_last_xfb_height;
return std::max((width - 1) / EFB_WIDTH + 1, (height - 1) / EFB_HEIGHT + 1);
source_height = std::round(source_width / target_aspect_ratio);
const u32 width_scale =
source_width > 0 ? ((target_width + (source_width - 1)) / source_width) : 1;
const u32 height_scale =
source_height > 0 ? ((target_height + (source_height - 1)) / source_height) : 1;
// Limit to the max to avoid creating textures larger than their max supported resolution.
return std::min(std::max(width_scale, height_scale),
static_cast<u32>(Config::Get(Config::GFX_MAX_EFB_SCALE)));
}
void Presenter::SetWindowSize(int width, int height)
void Presenter::SetSuggestedWindowSize(int width, int height)
{
// While trying to guess the best window resolution, we can't allow it to use the
// "AspectMode::Stretch" setting because that would self influence the output result,
// given it would be based on the previous frame resolution
const bool allow_stretch = false;
const auto [out_width, out_height] =
g_presenter->CalculateOutputDimensions(width, height, allow_stretch);
const auto [out_width, out_height] = CalculateOutputDimensions(width, height, allow_stretch);
// Track the last values of width/height to avoid sending a window resize event every frame.
if (out_width == m_last_window_request_width && out_height == m_last_window_request_height)
@ -415,7 +441,7 @@ void Presenter::SetWindowSize(int width, int height)
Host_RequestRenderWindowSize(out_width, out_height);
}
// Crop to exactly 16:9 or 4:3 if enabled and not AspectMode::Stretch.
// Crop to exact forced aspect ratios if enabled and not AspectMode::Stretch.
std::tuple<float, float> Presenter::ApplyStandardAspectCrop(float width, float height,
bool allow_stretch) const
{
@ -427,13 +453,28 @@ std::tuple<float, float> Presenter::ApplyStandardAspectCrop(float width, float h
if (!g_ActiveConfig.bCrop || aspect_mode == AspectMode::Stretch)
return {width, height};
// Force 4:3 or 16:9 by cropping the image.
// Force aspect ratios by cropping the image.
const float current_aspect = width / height;
const float expected_aspect =
(aspect_mode == AspectMode::AnalogWide ||
(aspect_mode == AspectMode::Auto && g_widescreen->IsGameWidescreen())) ?
(16.0f / 9.0f) :
(4.0f / 3.0f);
float expected_aspect;
switch (aspect_mode)
{
default:
case AspectMode::Auto:
expected_aspect = g_widescreen->IsGameWidescreen() ? (16.0f / 9.0f) : (4.0f / 3.0f);
break;
case AspectMode::ForceWide:
expected_aspect = 16.0f / 9.0f;
break;
case AspectMode::ForceStandard:
expected_aspect = 4.0f / 3.0f;
break;
// There should be no cropping needed in the custom case,
// as output should always exactly match the target aspect ratio
case AspectMode::Custom:
expected_aspect = g_ActiveConfig.GetCustomAspectRatio();
break;
}
if (current_aspect > expected_aspect)
{
// keep height, crop width
@ -458,11 +499,13 @@ void Presenter::UpdateDrawRectangle()
if (g_ActiveConfig.bWidescreenHack)
{
auto& vi = Core::System::GetInstance().GetVideoInterface();
float source_aspect = vi.GetAspectRatio();
float source_aspect_ratio = vi.GetAspectRatio();
// If the game is meant to be in widescreen (or forced to),
// scale the source aspect ratio to it.
if (g_widescreen->IsGameWidescreen())
source_aspect = AspectToWidescreen(source_aspect);
source_aspect_ratio = SourceAspectRatioToWidescreen(source_aspect_ratio);
const float adjust = source_aspect / draw_aspect_ratio;
const float adjust = source_aspect_ratio / draw_aspect_ratio;
if (adjust > 1)
{
// Vert+
@ -653,7 +696,7 @@ void Presenter::Present()
// Update the window size based on the frame that was just rendered.
// Due to depending on guest state, we need to call this every frame.
SetWindowSize(m_xfb_rect.GetWidth(), m_xfb_rect.GetHeight());
SetSuggestedWindowSize(m_xfb_rect.GetWidth(), m_xfb_rect.GetHeight());
}
return;
}
@ -695,7 +738,7 @@ void Presenter::Present()
{
// Update the window size based on the frame that was just rendered.
// Due to depending on guest state, we need to call this every frame.
SetWindowSize(m_xfb_rect.GetWidth(), m_xfb_rect.GetHeight());
SetSuggestedWindowSize(m_xfb_rect.GetWidth(), m_xfb_rect.GetHeight());
}
if (m_onscreen_ui)
@ -737,12 +780,12 @@ void Presenter::DoState(PointerWrap& p)
p.Do(m_last_xfb_stride);
p.Do(m_last_xfb_height);
if (p.IsReadMode())
// If we're loading and there is a last XFB, re-display it.
if (p.IsReadMode() && m_last_xfb_stride != 0)
{
// This technically counts as the end of the frame
AfterFrameEvent::Trigger();
// re-display the most recent XFB
ImmediateSwap(m_last_xfb_addr, m_last_xfb_width, m_last_xfb_stride, m_last_xfb_height,
m_last_xfb_ticks);
}

View file

@ -46,18 +46,20 @@ public:
void ConfigChanged(u32 changed_bits);
// Display resolution
// Window resolution (display resolution if fullscreen)
int GetBackbufferWidth() const { return m_backbuffer_width; }
int GetBackbufferHeight() const { return m_backbuffer_height; }
float GetBackbufferScale() const { return m_backbuffer_scale; }
u32 AutoIntegralScale() const;
AbstractTextureFormat GetBackbufferFormat() const { return m_backbuffer_format; }
void SetWindowSize(int width, int height);
void SetSuggestedWindowSize(int width, int height);
void SetBackbuffer(int backbuffer_width, int backbuffer_height);
void SetBackbuffer(SurfaceInfo info);
void OnBackbufferSet(bool size_changed, bool is_first_set);
void UpdateDrawRectangle();
// Returns the target aspect ratio the XFB output should be drawn with.
float CalculateDrawAspectRatio(bool allow_stretch = true) const;
// Crops the target rectangle to the framebuffer dimensions, reducing the size of the source
@ -103,6 +105,8 @@ private:
void ProcessFrameDumping(u64 ticks) const;
void OnBackBufferSizeChanged();
std::tuple<int, int> CalculateOutputDimensions(int width, int height,
bool allow_stretch = true) const;
std::tuple<float, float> ApplyStandardAspectCrop(float width, float height,
@ -126,15 +130,20 @@ private:
Common::Flag m_surface_changed;
Common::Flag m_surface_resized;
// The presentation rectangle.
// Width and height correspond to the final output resolution.
// Offsets imply black borders (if the window aspect ratio doesn't match the game's one).
MathUtil::Rectangle<int> m_target_rectangle = {};
u32 m_auto_resolution_scale = 1;
RcTcacheEntry m_xfb_entry;
MathUtil::Rectangle<int> m_xfb_rect;
// Tracking of XFB textures so we don't render duplicate frames.
u64 m_last_xfb_id = std::numeric_limits<u64>::max();
// These will be set on the first call to SetWindowSize.
// These will be set on the first call to SetSuggestedWindowSize.
int m_last_window_request_width = 0;
int m_last_window_request_height = 0;

View file

@ -120,47 +120,45 @@ void BlendingState::Generate(const BPMemory& bp)
const bool dstalpha = bp.dstalpha.enable && alphaupdate;
usedualsrc = true;
// The subtract bit has the highest priority
if (bp.blendmode.subtract)
if (bp.blendmode.blendenable)
{
blendenable = true;
subtractAlpha = subtract = true;
srcfactoralpha = srcfactor = SrcBlendFactor::One;
dstfactoralpha = dstfactor = DstBlendFactor::One;
if (dstalpha)
if (bp.blendmode.subtract)
{
subtractAlpha = false;
srcfactoralpha = SrcBlendFactor::One;
dstfactoralpha = DstBlendFactor::Zero;
blendenable = true;
subtractAlpha = subtract = true;
srcfactoralpha = srcfactor = SrcBlendFactor::One;
dstfactoralpha = dstfactor = DstBlendFactor::One;
if (dstalpha)
{
subtractAlpha = false;
srcfactoralpha = SrcBlendFactor::One;
dstfactoralpha = DstBlendFactor::Zero;
}
}
else
{
blendenable = true;
srcfactor = bp.blendmode.srcfactor;
dstfactor = bp.blendmode.dstfactor;
if (!target_has_alpha)
{
// uses ONE instead of DSTALPHA
srcfactor = RemoveDstAlphaUsage(srcfactor);
dstfactor = RemoveDstAlphaUsage(dstfactor);
}
// replaces SrcClr with SrcAlpha and DstClr with DstAlpha, it is important to
// use the dst function for the src factor and vice versa
srcfactoralpha = RemoveDstColorUsage(srcfactor);
dstfactoralpha = RemoveSrcColorUsage(dstfactor);
if (dstalpha)
{
srcfactoralpha = SrcBlendFactor::One;
dstfactoralpha = DstBlendFactor::Zero;
}
}
}
// The blendenable bit has the middle priority
else if (bp.blendmode.blendenable)
{
blendenable = true;
srcfactor = bp.blendmode.srcfactor;
dstfactor = bp.blendmode.dstfactor;
if (!target_has_alpha)
{
// uses ONE instead of DSTALPHA
srcfactor = RemoveDstAlphaUsage(srcfactor);
dstfactor = RemoveDstAlphaUsage(dstfactor);
}
// replaces SrcClr with SrcAlpha and DstClr with DstAlpha, it is important to
// use the dst function for the src factor and vice versa
srcfactoralpha = RemoveDstColorUsage(srcfactor);
dstfactoralpha = RemoveSrcColorUsage(dstfactor);
if (dstalpha)
{
srcfactoralpha = SrcBlendFactor::One;
dstfactoralpha = DstBlendFactor::Zero;
}
}
// The logicop bit has the lowest priority
else if (bp.blendmode.logicopenable)
{
if (bp.blendmode.logicmode == LogicOp::NoOp)

View file

@ -334,6 +334,7 @@ constexpr std::string_view CUSTOM_PIXELSHADER_COLOR_FUNC = "customShaderColor";
struct CustomPixelShader
{
std::string custom_shader;
std::string material_uniform_block;
bool operator==(const CustomPixelShader& other) const = default;
};

View file

@ -10,7 +10,7 @@
#include <string>
#include <utility>
#include <vector>
#if defined(_M_X86) || defined(_M_X86_64)
#if defined(_M_X86_64)
#include <pmmintrin.h>
#endif
@ -52,6 +52,7 @@
#include "VideoCommon/TextureConversionShader.h"
#include "VideoCommon/TextureConverterShaderGen.h"
#include "VideoCommon/TextureDecoder.h"
#include "VideoCommon/TextureUtils.h"
#include "VideoCommon/VertexManagerBase.h"
#include "VideoCommon/VideoCommon.h"
#include "VideoCommon/VideoConfig.h"
@ -414,7 +415,8 @@ void TextureCacheBase::ScaleTextureCacheEntryTo(RcTcacheEntry& entry, u32 new_wi
}
const TextureConfig newconfig(new_width, new_height, 1, entry->GetNumLayers(), 1,
AbstractTextureFormat::RGBA8, AbstractTextureFlag_RenderTarget);
AbstractTextureFormat::RGBA8, AbstractTextureFlag_RenderTarget,
AbstractTextureType::Texture_2DArray);
std::optional<TexPoolEntry> new_texture = AllocateTexture(newconfig);
if (!new_texture)
{
@ -444,7 +446,8 @@ bool TextureCacheBase::CheckReadbackTexture(u32 width, u32 height, AbstractTextu
return true;
}
TextureConfig staging_config(std::max(width, 128u), std::max(height, 128u), 1, 1, 1, format, 0);
TextureConfig staging_config(std::max(width, 128u), std::max(height, 128u), 1, 1, 1, format, 0,
AbstractTextureType::Texture_2DArray);
m_readback_texture.reset();
m_readback_texture = g_gfx->CreateStagingTexture(StagingTextureType::Readback, staging_config);
return m_readback_texture != nullptr;
@ -998,39 +1001,6 @@ RcTcacheEntry TextureCacheBase::DoPartialTextureUpdates(RcTcacheEntry& entry_to_
return entry_to_update;
}
void TextureCacheBase::DumpTexture(RcTcacheEntry& entry, std::string basename, unsigned int level,
bool is_arbitrary)
{
std::string szDir = File::GetUserPath(D_DUMPTEXTURES_IDX) + SConfig::GetInstance().GetGameID();
// make sure that the directory exists
if (!File::IsDirectory(szDir))
File::CreateDir(szDir);
if (is_arbitrary)
{
basename += "_arb";
}
if (level > 0)
{
if (!g_ActiveConfig.bDumpMipmapTextures)
return;
basename += fmt::format("_mip{}", level);
}
else
{
if (!g_ActiveConfig.bDumpBaseTextures)
return;
}
const std::string filename = fmt::format("{}/{}.png", szDir, basename);
if (File::Exists(filename))
return;
entry->texture->Save(filename, level, Config::Get(Config::GFX_TEXTURE_PNG_COMPRESSION_LEVEL));
}
// Helper for checking if a BPMemory TexMode0 register is set to Point
// Filtering modes. This is used to decide whether Anisotropic enhancements
// are (mostly) safe in the VideoBackends.
@ -1606,7 +1576,7 @@ RcTcacheEntry TextureCacheBase::GetTexture(const int textureCacheSafetyColorSamp
}
std::vector<VideoCommon::CachedAsset<VideoCommon::GameTextureAsset>> cached_game_assets;
std::vector<std::shared_ptr<VideoCommon::CustomTextureData>> data_for_assets;
std::vector<std::shared_ptr<VideoCommon::TextureData>> data_for_assets;
bool has_arbitrary_mipmaps = false;
bool skip_texture_dump = false;
std::shared_ptr<HiresTexture> hires_texture;
@ -1640,12 +1610,12 @@ RcTcacheEntry TextureCacheBase::GetTexture(const int textureCacheSafetyColorSamp
auto data = asset->GetData();
if (data)
{
if (!data->m_slices.empty())
if (!data->m_texture.m_slices.empty())
{
if (!data->m_slices[0].m_levels.empty())
if (!data->m_texture.m_slices[0].m_levels.empty())
{
height = data->m_slices[0].m_levels[0].height;
width = data->m_slices[0].m_levels[0].width;
height = data->m_texture.m_slices[0].m_levels[0].height;
width = data->m_texture.m_slices[0].m_levels[0].width;
}
}
}
@ -1660,6 +1630,7 @@ RcTcacheEntry TextureCacheBase::GetTexture(const int textureCacheSafetyColorSamp
}
}
data_for_assets.reserve(cached_game_assets.size());
for (auto& cached_asset : cached_game_assets)
{
auto data = cached_asset.m_asset->GetData();
@ -1667,7 +1638,7 @@ RcTcacheEntry TextureCacheBase::GetTexture(const int textureCacheSafetyColorSamp
{
if (cached_asset.m_asset->Validate(texture_info.GetRawWidth(), texture_info.GetRawHeight()))
{
data_for_assets.push_back(std::move(data));
data_for_assets.push_back(data);
}
}
}
@ -1688,7 +1659,7 @@ RcTcacheEntry TextureCacheBase::GetTexture(const int textureCacheSafetyColorSamp
RcTcacheEntry TextureCacheBase::CreateTextureEntry(
const TextureCreationInfo& creation_info, const TextureInfo& texture_info,
const int safety_color_sample_size,
std::vector<std::shared_ptr<VideoCommon::CustomTextureData>> assets_data,
std::vector<std::shared_ptr<VideoCommon::TextureData>> assets_data,
const bool custom_arbitrary_mipmaps, bool skip_texture_dump)
{
#ifdef __APPLE__
@ -1703,21 +1674,23 @@ RcTcacheEntry TextureCacheBase::CreateTextureEntry(
const auto calculate_max_levels = [&]() {
const auto max_element = std::max_element(
assets_data.begin(), assets_data.end(), [](const auto& lhs, const auto& rhs) {
return lhs->m_slices[0].m_levels.size() < rhs->m_slices[0].m_levels.size();
return lhs->m_texture.m_slices[0].m_levels.size() <
rhs->m_texture.m_slices[0].m_levels.size();
});
return max_element->get()->m_slices[0].m_levels.size();
return (*max_element)->m_texture.m_slices[0].m_levels.size();
};
const u32 texLevels = no_mips ? 1 : (u32)calculate_max_levels();
const auto& first_level = assets_data[0]->m_slices[0].m_levels[0];
const auto& first_level = assets_data[0]->m_texture.m_slices[0].m_levels[0];
const TextureConfig config(first_level.width, first_level.height, texLevels,
static_cast<u32>(assets_data.size()), 1, first_level.format, 0);
static_cast<u32>(assets_data.size()), 1, first_level.format, 0,
AbstractTextureType::Texture_2DArray);
entry = AllocateCacheEntry(config);
if (!entry) [[unlikely]]
return entry;
for (u32 data_index = 0; data_index < static_cast<u32>(assets_data.size()); data_index++)
{
const auto asset = assets_data[data_index];
const auto& slice = asset->m_slices[0];
const auto& asset = assets_data[data_index];
const auto& slice = asset->m_texture.m_slices[0];
for (u32 level_index = 0;
level_index < std::min(texLevels, static_cast<u32>(slice.m_levels.size()));
++level_index)
@ -1740,7 +1713,8 @@ RcTcacheEntry TextureCacheBase::CreateTextureEntry(
const u32 width = texture_info.GetRawWidth();
const u32 height = texture_info.GetRawHeight();
const TextureConfig config(width, height, texLevels, 1, 1, AbstractTextureFormat::RGBA8, 0);
const TextureConfig config(width, height, texLevels, 1, 1, AbstractTextureFormat::RGBA8, 0,
AbstractTextureType::Texture_2DArray);
entry = AllocateCacheEntry(config);
if (!entry) [[unlikely]]
return entry;
@ -1839,12 +1813,21 @@ RcTcacheEntry TextureCacheBase::CreateTextureEntry(
entry->has_arbitrary_mips = arbitrary_mip_detector.HasArbitraryMipmaps(dst_buffer);
if (g_ActiveConfig.bDumpTextures && !skip_texture_dump)
if (g_ActiveConfig.bDumpTextures && !skip_texture_dump && texLevels > 0)
{
const std::string basename = texture_info.CalculateTextureName().GetFullName();
for (u32 level = 0; level < texLevels; ++level)
if (g_ActiveConfig.bDumpBaseTextures)
{
DumpTexture(entry, basename, level, entry->has_arbitrary_mips);
VideoCommon::TextureUtils::DumpTexture(*entry->texture, basename, 0,
entry->has_arbitrary_mips);
}
if (g_ActiveConfig.bDumpMipmapTextures)
{
for (u32 level = 1; level < texLevels; ++level)
{
VideoCommon::TextureUtils::DumpTexture(*entry->texture, basename, level,
entry->has_arbitrary_mips);
}
}
}
}
@ -1917,7 +1900,8 @@ RcTcacheEntry TextureCacheBase::GetXFBTexture(u32 address, u32 width, u32 height
// Create a new VRAM texture, and fill it with the data from guest RAM.
entry = AllocateCacheEntry(TextureConfig(width, height, 1, 1, 1, AbstractTextureFormat::RGBA8,
AbstractTextureFlag_RenderTarget));
AbstractTextureFlag_RenderTarget,
AbstractTextureType::Texture_2DArray));
// Compute total texture size. XFB textures aren't tiled, so this is simple.
const u32 total_size = height * stride;
@ -2378,7 +2362,8 @@ void TextureCacheBase::CopyRenderTargetToTexture(
{
// create the texture
const TextureConfig config(scaled_tex_w, scaled_tex_h, 1, g_framebuffer_manager->GetEFBLayers(),
1, AbstractTextureFormat::RGBA8, AbstractTextureFlag_RenderTarget);
1, AbstractTextureFormat::RGBA8, AbstractTextureFlag_RenderTarget,
AbstractTextureType::Texture_2DArray);
entry = AllocateCacheEntry(config);
if (entry)
{
@ -2678,7 +2663,7 @@ void TextureCacheBase::UninitializeXFBMemory(u8* dst, u32 stride, u32 bytes_per_
// (Y=1,U=254,V=254) instead of dark green (Y=0,U=0,V=0) in YUV
// like is done in the EFB path.
#if defined(_M_X86) || defined(_M_X86_64)
#if defined(_M_X86_64)
__m128i sixteenBytes = _mm_set1_epi16((s16)(u16)0xFE01);
#endif
@ -2686,7 +2671,7 @@ void TextureCacheBase::UninitializeXFBMemory(u8* dst, u32 stride, u32 bytes_per_
{
u32 size = bytes_per_row;
u8* rowdst = dst;
#if defined(_M_X86) || defined(_M_X86_64)
#if defined(_M_X86_64)
while (size >= 16)
{
_mm_storeu_si128((__m128i*)rowdst, sixteenBytes);
@ -2863,7 +2848,8 @@ void TextureCacheBase::ReleaseToPool(TCacheEntry* entry)
bool TextureCacheBase::CreateUtilityTextures()
{
constexpr TextureConfig encoding_texture_config(
EFB_WIDTH * 4, 1024, 1, 1, 1, AbstractTextureFormat::BGRA8, AbstractTextureFlag_RenderTarget);
EFB_WIDTH * 4, 1024, 1, 1, 1, AbstractTextureFormat::BGRA8, AbstractTextureFlag_RenderTarget,
AbstractTextureType::Texture_2DArray);
m_efb_encoding_texture = g_gfx->CreateTexture(encoding_texture_config, "EFB encoding texture");
if (!m_efb_encoding_texture)
return false;
@ -2875,7 +2861,8 @@ bool TextureCacheBase::CreateUtilityTextures()
if (g_ActiveConfig.backend_info.bSupportsGPUTextureDecoding)
{
constexpr TextureConfig decoding_texture_config(
1024, 1024, 1, 1, 1, AbstractTextureFormat::RGBA8, AbstractTextureFlag_ComputeImage);
1024, 1024, 1, 1, 1, AbstractTextureFormat::RGBA8, AbstractTextureFlag_ComputeImage,
AbstractTextureType::Texture_2DArray);
m_decoding_texture =
g_gfx->CreateTexture(decoding_texture_config, "GPU texture decoding texture");
if (!m_decoding_texture)

View file

@ -349,7 +349,7 @@ private:
RcTcacheEntry
CreateTextureEntry(const TextureCreationInfo& creation_info, const TextureInfo& texture_info,
int safety_color_sample_size,
std::vector<std::shared_ptr<VideoCommon::CustomTextureData>> assets_data,
std::vector<std::shared_ptr<VideoCommon::TextureData>> assets_data,
bool custom_arbitrary_mipmaps, bool skip_texture_dump);
RcTcacheEntry GetXFBFromCache(u32 address, u32 width, u32 height, u32 stride);
@ -362,8 +362,6 @@ private:
TLUTFormat tlutfmt);
void StitchXFBCopy(RcTcacheEntry& entry_to_update);
void DumpTexture(RcTcacheEntry& entry, std::string basename, unsigned int level,
bool is_arbitrary);
void CheckTempSize(size_t required_size);
RcTcacheEntry AllocateCacheEntry(const TextureConfig& config);

View file

@ -39,16 +39,22 @@ enum AbstractTextureFlag : u32
{
AbstractTextureFlag_RenderTarget = (1 << 0), // Texture is used as a framebuffer.
AbstractTextureFlag_ComputeImage = (1 << 1), // Texture is used as a compute image.
AbstractTextureFlag_CubeMap = (1 << 2), // Texture is used as a cube map.
};
enum class AbstractTextureType
{
Texture_2DArray, // Used as a 2D texture array
Texture_2D, // Used as a normal 2D texture
Texture_CubeMap, // Used as a cube map texture
};
struct TextureConfig
{
constexpr TextureConfig() = default;
constexpr TextureConfig(u32 width_, u32 height_, u32 levels_, u32 layers_, u32 samples_,
AbstractTextureFormat format_, u32 flags_)
AbstractTextureFormat format_, u32 flags_, AbstractTextureType type_)
: width(width_), height(height_), levels(levels_), layers(layers_), samples(samples_),
format(format_), flags(flags_)
format(format_), flags(flags_), type(type_)
{
}
@ -62,7 +68,6 @@ struct TextureConfig
bool IsMultisampled() const { return samples > 1; }
bool IsRenderTarget() const { return (flags & AbstractTextureFlag_RenderTarget) != 0; }
bool IsComputeImage() const { return (flags & AbstractTextureFlag_ComputeImage) != 0; }
bool IsCubeMap() const { return (flags & AbstractTextureFlag_CubeMap) != 0; }
u32 width = 0;
u32 height = 0;
@ -71,6 +76,7 @@ struct TextureConfig
u32 samples = 1;
AbstractTextureFormat format = AbstractTextureFormat::RGBA8;
u32 flags = 0;
AbstractTextureType type = AbstractTextureType::Texture_2DArray;
};
namespace std

View file

@ -0,0 +1,41 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "VideoCommon/TextureUtils.h"
#include <fmt/format.h>
#include "Common/FileUtil.h"
#include "Core/Config/GraphicsSettings.h"
#include "Core/ConfigManager.h"
#include "VideoCommon/AbstractTexture.h"
namespace VideoCommon::TextureUtils
{
void DumpTexture(const ::AbstractTexture& texture, std::string basename, u32 level,
bool is_arbitrary)
{
std::string szDir = File::GetUserPath(D_DUMPTEXTURES_IDX) + SConfig::GetInstance().GetGameID();
// make sure that the directory exists
if (!File::IsDirectory(szDir))
File::CreateDir(szDir);
if (is_arbitrary)
{
basename += "_arb";
}
if (level > 0)
{
basename += fmt::format("_mip{}", level);
}
const std::string filename = fmt::format("{}/{}.png", szDir, basename);
if (File::Exists(filename))
return;
texture.Save(filename, level, Config::Get(Config::GFX_TEXTURE_PNG_COMPRESSION_LEVEL));
}
} // namespace VideoCommon::TextureUtils

View file

@ -0,0 +1,16 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string>
#include "Common/CommonTypes.h"
class AbstractTexture;
namespace VideoCommon::TextureUtils
{
void DumpTexture(const ::AbstractTexture& texture, std::string basename, u32 level,
bool is_arbitrary);
}

View file

@ -330,7 +330,7 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config,
out.Write("// {}\n", *uid_data);
WriteBitfieldExtractHeader(out, api_type, host_config);
WritePixelShaderCommonHeader(out, api_type, host_config, bounding_box);
WritePixelShaderCommonHeader(out, api_type, host_config, bounding_box, custom_details);
WriteCustomShaderStructDef(&out, numTexgen);
for (std::size_t i = 0; i < custom_details.shaders.size(); i++)
{

View file

@ -52,7 +52,7 @@ ShaderCode GenVertexShader(APIType api_type, const ShaderHostConfig& host_config
if (vertex_loader)
{
out.Write("UBO_BINDING(std140, 3) uniform GSBlock {{\n");
out.Write("UBO_BINDING(std140, 4) uniform GSBlock {{\n");
out.Write("{}", s_geometry_shader_uniforms);
out.Write("}};\n");
}
@ -84,7 +84,7 @@ SSBO_BINDING(1) readonly restrict buffer Vertices {{
// D3D12 uses a root constant for this uniform, since it changes with every draw.
// D3D11 doesn't currently support dynamic vertex loader, and we'll have to figure something
// out for it if we want to support it in the future.
out.Write("UBO_BINDING(std140, 4) uniform DX_Constants {{\n"
out.Write("UBO_BINDING(std140, 5) uniform DX_Constants {{\n"
" uint base_vertex;\n"
"}};\n\n"
"uint GetVertexBaseOffset(uint vertex_id) {{\n"
@ -101,7 +101,7 @@ SSBO_BINDING(1) readonly restrict buffer Vertices {{
out.Write(R"(
uint4 load_input_uint4_ubyte4(uint vtx_offset, uint attr_offset) {{
uint value = vertex_buffer[vtx_offset + attr_offset];
return uint4(value & 0xff, (value >> 8) & 0xff, (value >> 16) & 0xff, value >> 24);
return uint4(value & 0xffu, (value >> 8) & 0xffu, (value >> 16) & 0xffu, value >> 24);
}}
float4 load_input_float4_ubyte4(uint vtx_offset, uint attr_offset) {{

View file

@ -77,9 +77,6 @@ VertexLoader::VertexLoader(const TVtxDesc& vtx_desc, const VAT& vtx_attr)
void VertexLoader::CompileVertexTranslator()
{
// Reset pipeline
m_numPipelineStages = 0;
// Position in pc vertex format.
int nat_offset = 0;
@ -149,9 +146,16 @@ void VertexLoader::CompileVertexTranslator()
VertexLoader_Color::GetFunction(m_VtxDesc.low.Color[i], m_VtxAttr.GetColorFormat(i));
if (pFunc != nullptr)
{
WriteCall(pFunc);
}
else
{
ASSERT(m_VtxDesc.low.Color[i] == VertexComponentFormat::NotPresent);
// Keep colIndex in sync if color 0 is absent but color 1 is present
if (i == 0 && m_VtxDesc.low.Color[1] != VertexComponentFormat::NotPresent)
WriteCall(VertexLoader_Color::GetDummyFunction());
}
if (m_VtxDesc.low.Color[i] != VertexComponentFormat::NotPresent)
{
@ -213,12 +217,13 @@ void VertexLoader::CompileVertexTranslator()
{
// if there's more tex coords later, have to write a dummy call
bool has_more = false;
for (size_t j = 0; j < m_VtxDesc.high.TexCoord.Size(); ++j)
for (size_t j = i + 1; j < m_VtxDesc.high.TexCoord.Size(); ++j)
{
if (m_VtxDesc.high.TexCoord[j] != VertexComponentFormat::NotPresent)
{
has_more = true;
WriteCall(VertexLoader_TextCoord::GetDummyFunction()); // important to get indices right!
// Keep tcIndex in sync so that the correct array is used later
WriteCall(VertexLoader_TextCoord::GetDummyFunction());
break;
}
else if (m_VtxDesc.low.TexMatIdx[j])
@ -245,7 +250,7 @@ void VertexLoader::CompileVertexTranslator()
void VertexLoader::WriteCall(TPipelineFunction func)
{
m_PipelineStages[m_numPipelineStages++] = func;
m_PipelineStages.push_back(func);
}
int VertexLoader::RunVertices(const u8* src, u8* dst, int count)
@ -261,8 +266,8 @@ int VertexLoader::RunVertices(const u8* src, u8* dst, int count)
m_tcIndex = 0;
m_colIndex = 0;
m_texmtxwrite = m_texmtxread = 0;
for (int i = 0; i < m_numPipelineStages; i++)
m_PipelineStages[i](this);
for (TPipelineFunction& func : m_PipelineStages)
func(this);
PRIM_LOG("\n");
}

View file

@ -9,6 +9,7 @@
#include <string>
#include "Common/CommonTypes.h"
#include "Common/SmallVector.h"
#include "VideoCommon/VertexLoaderBase.h"
class VertexLoader;
@ -38,8 +39,11 @@ public:
private:
// Pipeline.
TPipelineFunction m_PipelineStages[64]; // TODO - figure out real max. it's lower.
int m_numPipelineStages;
// 1 pos matrix + 8 texture matrices + 1 position + 1 normal or normal/binormal/tangent
// + 2 colors + 8 texture coordinates or dummy texture coordinates + 8 texture matrices
// merged into texture coordinates + 1 skip gives a maximum of 30
// (Tested by VertexLoaderTest.LargeFloatVertexSpeed)
Common::SmallVector<TPipelineFunction, 30> m_PipelineStages;
void CompileVertexTranslator();

View file

@ -56,7 +56,7 @@ VertexLoaderARM64::VertexLoaderARM64(const TVtxDesc& vtx_desc, const VAT& vtx_at
const Common::ScopedJITPageWriteAndNoExecute enable_jit_page_writes;
ClearCodeSpace();
GenerateVertexLoader();
WriteProtect();
WriteProtect(true);
}
// Returns the register to use as the base and an offset from that register.
@ -82,8 +82,8 @@ std::pair<Arm64Gen::ARM64Reg, u32> VertexLoaderARM64::GetVertexAddr(CPArray arra
if (array == CPArray::Position)
{
EOR(scratch2_reg, scratch1_reg,
attribute == VertexComponentFormat::Index8 ? LogicalImm(0xFF, 32) :
LogicalImm(0xFFFF, 32));
attribute == VertexComponentFormat::Index8 ? LogicalImm(0xFF, GPRSize::B32) :
LogicalImm(0xFFFF, GPRSize::B32));
m_skip_vertex = CBZ(scratch2_reg);
}
@ -202,7 +202,7 @@ void VertexLoaderARM64::ReadColor(VertexComponentFormat attribute, ColorFormat f
LDUR(scratch2_reg, reg, offset);
if (format != ColorFormat::RGBA8888)
ORR(scratch2_reg, scratch2_reg, LogicalImm(0xFF000000, 32));
ORR(scratch2_reg, scratch2_reg, LogicalImm(0xFF000000, GPRSize::B32));
STR(IndexType::Unsigned, scratch2_reg, dst_reg, m_dst_ofs);
load_bytes = format == ColorFormat::RGB888 ? 3 : 4;
break;
@ -215,7 +215,7 @@ void VertexLoaderARM64::ReadColor(VertexComponentFormat attribute, ColorFormat f
REV16(scratch3_reg, scratch3_reg);
// B
AND(scratch2_reg, scratch3_reg, LogicalImm(0x1F, 32));
AND(scratch2_reg, scratch3_reg, LogicalImm(0x1F, GPRSize::B32));
ORR(scratch2_reg, ARM64Reg::WSP, scratch2_reg, ArithOption(scratch2_reg, ShiftType::LSL, 3));
ORR(scratch2_reg, scratch2_reg, scratch2_reg, ArithOption(scratch2_reg, ShiftType::LSR, 5));
ORR(scratch1_reg, ARM64Reg::WSP, scratch2_reg, ArithOption(scratch2_reg, ShiftType::LSL, 16));
@ -232,7 +232,7 @@ void VertexLoaderARM64::ReadColor(VertexComponentFormat attribute, ColorFormat f
ORR(scratch1_reg, scratch1_reg, scratch2_reg, ArithOption(scratch2_reg, ShiftType::LSR, 2));
// A
ORR(scratch1_reg, scratch1_reg, LogicalImm(0xFF000000, 32));
ORR(scratch1_reg, scratch1_reg, LogicalImm(0xFF000000, GPRSize::B32));
STR(IndexType::Unsigned, scratch1_reg, dst_reg, m_dst_ofs);
load_bytes = 2;
@ -248,7 +248,7 @@ void VertexLoaderARM64::ReadColor(VertexComponentFormat attribute, ColorFormat f
UBFM(scratch1_reg, scratch3_reg, 4, 7);
// G
AND(scratch2_reg, scratch3_reg, LogicalImm(0xF, 32));
AND(scratch2_reg, scratch3_reg, LogicalImm(0xF, GPRSize::B32));
ORR(scratch1_reg, scratch1_reg, scratch2_reg, ArithOption(scratch2_reg, ShiftType::LSL, 8));
// B
@ -356,7 +356,7 @@ void VertexLoaderARM64::GenerateVertexLoader()
if (m_VtxDesc.low.PosMatIdx)
{
LDRB(IndexType::Unsigned, scratch1_reg, src_reg, m_src_ofs);
AND(scratch1_reg, scratch1_reg, LogicalImm(0x3F, 32));
AND(scratch1_reg, scratch1_reg, LogicalImm(0x3F, GPRSize::B32));
STR(IndexType::Unsigned, scratch1_reg, dst_reg, m_dst_ofs);
// Z-Freeze

View file

@ -49,7 +49,7 @@ VertexLoaderX64::VertexLoaderX64(const TVtxDesc& vtx_desc, const VAT& vtx_att)
AllocCodeSpace(4096);
ClearCodeSpace();
GenerateVertexLoader();
WriteProtect();
WriteProtect(true);
Common::JitRegister::Register(region, GetCodePtr(), "VertexLoaderX64\nVtx desc: \n{}\nVAT:\n{}",
vtx_desc, vtx_att);

View file

@ -16,6 +16,11 @@
namespace
{
void Color_Read_Dummy(VertexLoader* loader)
{
loader->m_colIndex++;
}
constexpr u32 alpha_mask = 0xFF000000;
void SetCol(VertexLoader* loader, u32 val)
@ -201,3 +206,8 @@ TPipelineFunction VertexLoader_Color::GetFunction(VertexComponentFormat type, Co
}
return s_table_read_color[type][format];
}
TPipelineFunction VertexLoader_Color::GetDummyFunction()
{
return Color_Read_Dummy;
}

View file

@ -25,6 +25,9 @@ public:
static TPipelineFunction GetFunction(VertexComponentFormat type, ColorFormat format);
// It is important to synchronize colIndex, or else the wrong color array will be used
static TPipelineFunction GetDummyFunction();
private:
template <typename T, auto last_member>
using EnumMap = typename Common::EnumMap<T, last_member>;

View file

@ -22,7 +22,7 @@ public:
static TPipelineFunction GetFunction(VertexComponentFormat type, ComponentFormat format,
TexComponentCount elements);
// It is important to synchronize tcIndex.
// It is important to synchronize tcIndex, or else the wrong texture coordinate array will be used
static TPipelineFunction GetDummyFunction();
private:

View file

@ -42,6 +42,7 @@
#include "VideoCommon/VideoCommon.h"
#include "VideoCommon/VideoConfig.h"
#include "VideoCommon/XFMemory.h"
#include "VideoCommon/XFStateManager.h"
std::unique_ptr<VertexManagerBase> g_vertex_manager;
@ -90,8 +91,9 @@ static bool IsAnamorphicProjection(const Projection::Raw& projection, const View
const VideoConfig& config)
{
// If ratio between our projection and viewport aspect ratios is similar to 16:9 / 4:3
// we have an anamorphic projection. This value can be overridden
// by a GameINI.
// we have an anamorphic projection. This value can be overridden by a GameINI.
// Game cheats that change the aspect ratio to natively unsupported ones
// won't be automatically recognized here.
return std::abs(CalculateProjectionViewportRatio(projection, viewport) -
config.widescreen_heuristic_widescreen_ratio) <
@ -539,6 +541,7 @@ void VertexManagerBase::Flush()
auto& pixel_shader_manager = system.GetPixelShaderManager();
auto& geometry_shader_manager = system.GetGeometryShaderManager();
auto& vertex_shader_manager = system.GetVertexShaderManager();
auto& xf_state_manager = system.GetXFStateManager();
if (g_ActiveConfig.bGraphicMods)
{
@ -578,7 +581,7 @@ void VertexManagerBase::Flush()
}
}
}
vertex_shader_manager.SetConstants(texture_names);
vertex_shader_manager.SetConstants(texture_names, xf_state_manager);
if (!bpmem.genMode.zfreeze)
{
// Must be done after VertexShaderManager::SetConstants()
@ -595,12 +598,14 @@ void VertexManagerBase::Flush()
CustomPixelShaderContents custom_pixel_shader_contents;
std::optional<CustomPixelShader> custom_pixel_shader;
std::vector<std::string> custom_pixel_texture_names;
for (int i = 0; i < texture_names.size(); i++)
std::span<u8> custom_pixel_shader_uniforms;
for (size_t i = 0; i < texture_names.size(); i++)
{
const std::string& texture_name = texture_names[i];
const u32 texture_unit = texture_units[i];
bool skip = false;
GraphicsModActionData::DrawStarted draw_started{texture_unit, &skip, &custom_pixel_shader};
GraphicsModActionData::DrawStarted draw_started{texture_unit, &skip, &custom_pixel_shader,
&custom_pixel_shader_uniforms};
for (const auto& action : g_graphics_mod_manager->GetDrawStartedActions(texture_name))
{
action->OnDrawStarted(&draw_started);
@ -644,6 +649,12 @@ void VertexManagerBase::Flush()
// Now we can upload uniforms, as nothing else will override them.
geometry_shader_manager.SetConstants(m_current_primitive_type);
pixel_shader_manager.SetConstants();
if (!custom_pixel_shader_uniforms.empty() &&
pixel_shader_manager.custom_constants.data() != custom_pixel_shader_uniforms.data())
{
pixel_shader_manager.custom_constants_dirty = true;
}
pixel_shader_manager.custom_constants = custom_pixel_shader_uniforms;
UploadUniforms();
// Update the pipeline, or compile one if needed.
@ -1052,7 +1063,8 @@ void VertexManagerBase::OnEndFrame()
return;
// In order to reduce CPU readback latency, we want to kick a command buffer roughly halfway
// between the draw counters that invoked the readback, or every 250 draws, whichever is smaller.
// between the draw counters that invoked the readback, or every 250 draws, whichever is
// smaller.
if (g_ActiveConfig.iCommandBufferExecuteInterval > 0)
{
u32 last_draw_counter = 0;

View file

@ -96,14 +96,14 @@ ShaderCode GenerateVertexShaderCode(APIType api_type, const ShaderHostConfig& ho
if (uid_data->vs_expand != VSExpand::None)
{
out.Write("UBO_BINDING(std140, 3) uniform GSBlock {{\n");
out.Write("UBO_BINDING(std140, 4) uniform GSBlock {{\n");
out.Write("{}", s_geometry_shader_uniforms);
out.Write("}};\n");
if (api_type == APIType::D3D)
{
// D3D doesn't include the base vertex in SV_VertexID
out.Write("UBO_BINDING(std140, 4) uniform DX_Constants {{\n"
out.Write("UBO_BINDING(std140, 5) uniform DX_Constants {{\n"
" uint base_vertex;\n"
"}};\n\n");
}
@ -149,7 +149,7 @@ ShaderCode GenerateVertexShaderCode(APIType api_type, const ShaderHostConfig& ho
// Can't use float3, etc because we want 4-byte alignment
out.Write(
"uint4 unpack_ubyte4(uint value) {{\n"
" return uint4(value & 0xff, (value >> 8) & 0xff, (value >> 16) & 0xff, value >> 24);\n"
" return uint4(value & 0xffu, (value >> 8) & 0xffu, (value >> 16) & 0xffu, value >> 24);\n"
"}}\n\n"
"struct InputData {{\n");
if (uid_data->components & VB_HAS_POSMTXIDX)
@ -271,7 +271,7 @@ ShaderCode GenerateVertexShaderCode(APIType api_type, const ShaderHostConfig& ho
if (api_type == APIType::D3D)
out.Write("uint vertex_id = (gl_VertexID >> 2) + base_vertex;\n");
else
out.Write("uint vertex_id = gl_VertexID >> 2;\n");
out.Write("uint vertex_id = uint(gl_VertexID) >> 2u;\n");
out.Write("InputData i = input_buffer[vertex_id];\n"
"{}",
input_extract.GetBuffer());
@ -524,9 +524,9 @@ ShaderCode GenerateVertexShaderCode(APIType api_type, const ShaderHostConfig& ho
out.Write("// Line expansion\n"
"uint other_id = vertex_id;\n"
"if (is_bottom) {{\n"
" other_id -= 1;\n"
" other_id -= 1u;\n"
"}} else {{\n"
" other_id += 1;\n"
" other_id += 1u;\n"
"}}\n"
"InputData other = input_buffer[other_id];\n");
if (uid_data->position_has_3_elems)

View file

@ -30,24 +30,13 @@
#include "VideoCommon/VideoCommon.h"
#include "VideoCommon/VideoConfig.h"
#include "VideoCommon/XFMemory.h"
#include "VideoCommon/XFStateManager.h"
void VertexShaderManager::Init()
{
// Initialize state tracking variables
m_minmax_transform_matrices_changed.fill(-1);
m_minmax_normal_matrices_changed.fill(-1);
m_minmax_post_transform_matrices_changed.fill(-1);
m_minmax_lights_changed.fill(-1);
m_materials_changed = BitSet32(0);
m_tex_matrices_changed.fill(false);
m_pos_normal_matrix_changed = false;
m_projection_changed = true;
m_viewport_changed = false;
m_tex_mtx_info_changed = false;
m_lighting_config_changed = false;
m_projection_graphics_mod_change = false;
std::memset(static_cast<void*>(&xfmem), 0, sizeof(xfmem));
constants = {};
// TODO: should these go inside ResetView()?
@ -57,15 +46,6 @@ void VertexShaderManager::Init()
dirty = true;
}
void VertexShaderManager::Dirty()
{
// This function is called after a savestate is loaded.
// Any constants that can changed based on settings should be re-calculated
m_projection_changed = true;
dirty = true;
}
Common::Matrix44 VertexShaderManager::LoadProjectionMatrix()
{
const auto& rawProjection = xfmem.projection.rawProjection;
@ -147,11 +127,11 @@ Common::Matrix44 VertexShaderManager::LoadProjectionMatrix()
return corrected_matrix;
}
void VertexShaderManager::SetProjectionMatrix()
void VertexShaderManager::SetProjectionMatrix(XFStateManager& xf_state_manager)
{
if (m_projection_changed || g_freelook_camera.GetController()->IsDirty())
if (xf_state_manager.DidProjectionChange() || g_freelook_camera.GetController()->IsDirty())
{
m_projection_changed = false;
xf_state_manager.ResetProjection();
auto corrected_matrix = LoadProjectionMatrix();
memcpy(constants.projection.data(), corrected_matrix.data.data(), 4 * sizeof(float4));
}
@ -178,7 +158,8 @@ bool VertexShaderManager::UseVertexDepthRange()
// Syncs the shader constant buffers with xfmem
// TODO: A cleaner way to control the matrices without making a mess in the parameters field
void VertexShaderManager::SetConstants(const std::vector<std::string>& textures)
void VertexShaderManager::SetConstants(const std::vector<std::string>& textures,
XFStateManager& xf_state_manager)
{
if (constants.missing_color_hex != g_ActiveConfig.iMissingColorValue)
{
@ -192,44 +173,50 @@ void VertexShaderManager::SetConstants(const std::vector<std::string>& textures)
dirty = true;
}
if (m_minmax_transform_matrices_changed[0] >= 0)
const auto per_vertex_transform_matrix_changes =
xf_state_manager.GetPerVertexTransformMatrixChanges();
if (per_vertex_transform_matrix_changes[0] >= 0)
{
int startn = m_minmax_transform_matrices_changed[0] / 4;
int endn = (m_minmax_transform_matrices_changed[1] + 3) / 4;
int startn = per_vertex_transform_matrix_changes[0] / 4;
int endn = (per_vertex_transform_matrix_changes[1] + 3) / 4;
memcpy(constants.transformmatrices[startn].data(), &xfmem.posMatrices[startn * 4],
(endn - startn) * sizeof(float4));
dirty = true;
m_minmax_transform_matrices_changed[0] = m_minmax_transform_matrices_changed[1] = -1;
xf_state_manager.ResetPerVertexTransformMatrixChanges();
}
if (m_minmax_normal_matrices_changed[0] >= 0)
const auto per_vertex_normal_matrices_changed =
xf_state_manager.GetPerVertexNormalMatrixChanges();
if (per_vertex_normal_matrices_changed[0] >= 0)
{
int startn = m_minmax_normal_matrices_changed[0] / 3;
int endn = (m_minmax_normal_matrices_changed[1] + 2) / 3;
int startn = per_vertex_normal_matrices_changed[0] / 3;
int endn = (per_vertex_normal_matrices_changed[1] + 2) / 3;
for (int i = startn; i < endn; i++)
{
memcpy(constants.normalmatrices[i].data(), &xfmem.normalMatrices[3 * i], 12);
}
dirty = true;
m_minmax_normal_matrices_changed[0] = m_minmax_normal_matrices_changed[1] = -1;
xf_state_manager.ResetPerVertexNormalMatrixChanges();
}
if (m_minmax_post_transform_matrices_changed[0] >= 0)
const auto post_transform_matrices_changed = xf_state_manager.GetPostTransformMatrixChanges();
if (post_transform_matrices_changed[0] >= 0)
{
int startn = m_minmax_post_transform_matrices_changed[0] / 4;
int endn = (m_minmax_post_transform_matrices_changed[1] + 3) / 4;
int startn = post_transform_matrices_changed[0] / 4;
int endn = (post_transform_matrices_changed[1] + 3) / 4;
memcpy(constants.posttransformmatrices[startn].data(), &xfmem.postMatrices[startn * 4],
(endn - startn) * sizeof(float4));
dirty = true;
m_minmax_post_transform_matrices_changed[0] = m_minmax_post_transform_matrices_changed[1] = -1;
xf_state_manager.ResetPostTransformMatrixChanges();
}
if (m_minmax_lights_changed[0] >= 0)
const auto light_changes = xf_state_manager.GetLightsChanged();
if (light_changes[0] >= 0)
{
// TODO: Outdated comment
// lights don't have a 1 to 1 mapping, the color component needs to be converted to 4 floats
int istart = m_minmax_lights_changed[0] / 0x10;
int iend = (m_minmax_lights_changed[1] + 15) / 0x10;
const int istart = light_changes[0] / 0x10;
const int iend = (light_changes[1] + 15) / 0x10;
for (int i = istart; i < iend; ++i)
{
@ -282,10 +269,10 @@ void VertexShaderManager::SetConstants(const std::vector<std::string>& textures)
}
dirty = true;
m_minmax_lights_changed[0] = m_minmax_lights_changed[1] = -1;
xf_state_manager.ResetLightsChanged();
}
for (int i : m_materials_changed)
for (int i : xf_state_manager.GetMaterialChanges())
{
u32 data = i >= 2 ? xfmem.matColor[i - 2] : xfmem.ambColor[i];
constants.materials[i][0] = (data >> 24) & 0xFF;
@ -294,12 +281,11 @@ void VertexShaderManager::SetConstants(const std::vector<std::string>& textures)
constants.materials[i][3] = data & 0xFF;
dirty = true;
}
m_materials_changed = BitSet32(0);
xf_state_manager.ResetMaterialChanges();
if (m_pos_normal_matrix_changed)
if (xf_state_manager.DidPosNormalChange())
{
m_pos_normal_matrix_changed = false;
xf_state_manager.ResetPosNormalChange();
const float* pos = &xfmem.posMatrices[g_main_cp_state.matrix_index_a.PosNormalMtxIdx * 4];
const float* norm =
&xfmem.normalMatrices[3 * (g_main_cp_state.matrix_index_a.PosNormalMtxIdx & 31)];
@ -311,9 +297,9 @@ void VertexShaderManager::SetConstants(const std::vector<std::string>& textures)
dirty = true;
}
if (m_tex_matrices_changed[0])
if (xf_state_manager.DidTexMatrixAChange())
{
m_tex_matrices_changed[0] = false;
xf_state_manager.ResetTexMatrixAChange();
const std::array<const float*, 4> pos_matrix_ptrs{
&xfmem.posMatrices[g_main_cp_state.matrix_index_a.Tex0MtxIdx * 4],
&xfmem.posMatrices[g_main_cp_state.matrix_index_a.Tex1MtxIdx * 4],
@ -328,9 +314,9 @@ void VertexShaderManager::SetConstants(const std::vector<std::string>& textures)
dirty = true;
}
if (m_tex_matrices_changed[1])
if (xf_state_manager.DidTexMatrixBChange())
{
m_tex_matrices_changed[1] = false;
xf_state_manager.ResetTexMatrixBChange();
const std::array<const float*, 4> pos_matrix_ptrs{
&xfmem.posMatrices[g_main_cp_state.matrix_index_b.Tex4MtxIdx * 4],
&xfmem.posMatrices[g_main_cp_state.matrix_index_b.Tex5MtxIdx * 4],
@ -345,9 +331,9 @@ void VertexShaderManager::SetConstants(const std::vector<std::string>& textures)
dirty = true;
}
if (m_viewport_changed)
if (xf_state_manager.DidViewportChange())
{
m_viewport_changed = false;
xf_state_manager.ResetViewportChange();
// The console GPU places the pixel center at 7/12 unless antialiasing
// is enabled, while D3D and OpenGL place it at 0.5. See the comment
@ -421,10 +407,10 @@ void VertexShaderManager::SetConstants(const std::vector<std::string>& textures)
}
}
if (m_projection_changed || g_freelook_camera.GetController()->IsDirty() ||
if (xf_state_manager.DidProjectionChange() || g_freelook_camera.GetController()->IsDirty() ||
!projection_actions.empty() || m_projection_graphics_mod_change)
{
m_projection_changed = false;
xf_state_manager.ResetProjection();
m_projection_graphics_mod_change = !projection_actions.empty();
auto corrected_matrix = LoadProjectionMatrix();
@ -436,13 +422,12 @@ void VertexShaderManager::SetConstants(const std::vector<std::string>& textures)
}
memcpy(constants.projection.data(), corrected_matrix.data.data(), 4 * sizeof(float4));
dirty = true;
}
if (m_tex_mtx_info_changed)
if (xf_state_manager.DidTexMatrixInfoChange())
{
m_tex_mtx_info_changed = false;
xf_state_manager.ResetTexMatrixInfoChange();
constants.xfmem_dualTexInfo = xfmem.dualTexTrans.enabled;
for (size_t i = 0; i < std::size(xfmem.texMtxInfo); i++)
constants.xfmem_pack1[i][0] = xfmem.texMtxInfo[i].hex;
@ -452,9 +437,9 @@ void VertexShaderManager::SetConstants(const std::vector<std::string>& textures)
dirty = true;
}
if (m_lighting_config_changed)
if (xf_state_manager.DidLightingConfigChange())
{
m_lighting_config_changed = false;
xf_state_manager.ResetLightingConfigChange();
for (size_t i = 0; i < 2; i++)
{
@ -466,173 +451,6 @@ void VertexShaderManager::SetConstants(const std::vector<std::string>& textures)
}
}
void VertexShaderManager::InvalidateXFRange(int start, int end)
{
if (((u32)start >= (u32)g_main_cp_state.matrix_index_a.PosNormalMtxIdx * 4 &&
(u32)start < (u32)g_main_cp_state.matrix_index_a.PosNormalMtxIdx * 4 + 12) ||
((u32)start >=
XFMEM_NORMALMATRICES + ((u32)g_main_cp_state.matrix_index_a.PosNormalMtxIdx & 31) * 3 &&
(u32)start < XFMEM_NORMALMATRICES +
((u32)g_main_cp_state.matrix_index_a.PosNormalMtxIdx & 31) * 3 + 9))
{
m_pos_normal_matrix_changed = true;
}
if (((u32)start >= (u32)g_main_cp_state.matrix_index_a.Tex0MtxIdx * 4 &&
(u32)start < (u32)g_main_cp_state.matrix_index_a.Tex0MtxIdx * 4 + 12) ||
((u32)start >= (u32)g_main_cp_state.matrix_index_a.Tex1MtxIdx * 4 &&
(u32)start < (u32)g_main_cp_state.matrix_index_a.Tex1MtxIdx * 4 + 12) ||
((u32)start >= (u32)g_main_cp_state.matrix_index_a.Tex2MtxIdx * 4 &&
(u32)start < (u32)g_main_cp_state.matrix_index_a.Tex2MtxIdx * 4 + 12) ||
((u32)start >= (u32)g_main_cp_state.matrix_index_a.Tex3MtxIdx * 4 &&
(u32)start < (u32)g_main_cp_state.matrix_index_a.Tex3MtxIdx * 4 + 12))
{
m_tex_matrices_changed[0] = true;
}
if (((u32)start >= (u32)g_main_cp_state.matrix_index_b.Tex4MtxIdx * 4 &&
(u32)start < (u32)g_main_cp_state.matrix_index_b.Tex4MtxIdx * 4 + 12) ||
((u32)start >= (u32)g_main_cp_state.matrix_index_b.Tex5MtxIdx * 4 &&
(u32)start < (u32)g_main_cp_state.matrix_index_b.Tex5MtxIdx * 4 + 12) ||
((u32)start >= (u32)g_main_cp_state.matrix_index_b.Tex6MtxIdx * 4 &&
(u32)start < (u32)g_main_cp_state.matrix_index_b.Tex6MtxIdx * 4 + 12) ||
((u32)start >= (u32)g_main_cp_state.matrix_index_b.Tex7MtxIdx * 4 &&
(u32)start < (u32)g_main_cp_state.matrix_index_b.Tex7MtxIdx * 4 + 12))
{
m_tex_matrices_changed[1] = true;
}
if (start < XFMEM_POSMATRICES_END)
{
if (m_minmax_transform_matrices_changed[0] == -1)
{
m_minmax_transform_matrices_changed[0] = start;
m_minmax_transform_matrices_changed[1] =
end > XFMEM_POSMATRICES_END ? XFMEM_POSMATRICES_END : end;
}
else
{
if (m_minmax_transform_matrices_changed[0] > start)
m_minmax_transform_matrices_changed[0] = start;
if (m_minmax_transform_matrices_changed[1] < end)
m_minmax_transform_matrices_changed[1] =
end > XFMEM_POSMATRICES_END ? XFMEM_POSMATRICES_END : end;
}
}
if (start < XFMEM_NORMALMATRICES_END && end > XFMEM_NORMALMATRICES)
{
int _start = start < XFMEM_NORMALMATRICES ? 0 : start - XFMEM_NORMALMATRICES;
int _end = end < XFMEM_NORMALMATRICES_END ? end - XFMEM_NORMALMATRICES :
XFMEM_NORMALMATRICES_END - XFMEM_NORMALMATRICES;
if (m_minmax_normal_matrices_changed[0] == -1)
{
m_minmax_normal_matrices_changed[0] = _start;
m_minmax_normal_matrices_changed[1] = _end;
}
else
{
if (m_minmax_normal_matrices_changed[0] > _start)
m_minmax_normal_matrices_changed[0] = _start;
if (m_minmax_normal_matrices_changed[1] < _end)
m_minmax_normal_matrices_changed[1] = _end;
}
}
if (start < XFMEM_POSTMATRICES_END && end > XFMEM_POSTMATRICES)
{
int _start = start < XFMEM_POSTMATRICES ? XFMEM_POSTMATRICES : start - XFMEM_POSTMATRICES;
int _end = end < XFMEM_POSTMATRICES_END ? end - XFMEM_POSTMATRICES :
XFMEM_POSTMATRICES_END - XFMEM_POSTMATRICES;
if (m_minmax_post_transform_matrices_changed[0] == -1)
{
m_minmax_post_transform_matrices_changed[0] = _start;
m_minmax_post_transform_matrices_changed[1] = _end;
}
else
{
if (m_minmax_post_transform_matrices_changed[0] > _start)
m_minmax_post_transform_matrices_changed[0] = _start;
if (m_minmax_post_transform_matrices_changed[1] < _end)
m_minmax_post_transform_matrices_changed[1] = _end;
}
}
if (start < XFMEM_LIGHTS_END && end > XFMEM_LIGHTS)
{
int _start = start < XFMEM_LIGHTS ? XFMEM_LIGHTS : start - XFMEM_LIGHTS;
int _end = end < XFMEM_LIGHTS_END ? end - XFMEM_LIGHTS : XFMEM_LIGHTS_END - XFMEM_LIGHTS;
if (m_minmax_lights_changed[0] == -1)
{
m_minmax_lights_changed[0] = _start;
m_minmax_lights_changed[1] = _end;
}
else
{
if (m_minmax_lights_changed[0] > _start)
m_minmax_lights_changed[0] = _start;
if (m_minmax_lights_changed[1] < _end)
m_minmax_lights_changed[1] = _end;
}
}
}
void VertexShaderManager::SetTexMatrixChangedA(u32 Value)
{
if (g_main_cp_state.matrix_index_a.Hex != Value)
{
g_vertex_manager->Flush();
if (g_main_cp_state.matrix_index_a.PosNormalMtxIdx != (Value & 0x3f))
m_pos_normal_matrix_changed = true;
m_tex_matrices_changed[0] = true;
g_main_cp_state.matrix_index_a.Hex = Value;
}
}
void VertexShaderManager::SetTexMatrixChangedB(u32 Value)
{
if (g_main_cp_state.matrix_index_b.Hex != Value)
{
g_vertex_manager->Flush();
m_tex_matrices_changed[1] = true;
g_main_cp_state.matrix_index_b.Hex = Value;
}
}
void VertexShaderManager::SetViewportChanged()
{
m_viewport_changed = true;
}
void VertexShaderManager::SetProjectionChanged()
{
m_projection_changed = true;
}
void VertexShaderManager::SetMaterialColorChanged(int index)
{
m_materials_changed[index] = true;
}
void VertexShaderManager::SetTexMatrixInfoChanged(int index)
{
// TODO: Should we track this with more precision, like which indices changed?
// The whole vertex constants are probably going to be uploaded regardless.
m_tex_mtx_info_changed = true;
}
void VertexShaderManager::SetLightingConfigChanged()
{
m_lighting_config_changed = true;
}
void VertexShaderManager::TransformToClipSpace(const float* data, float* out, u32 MtxIdx)
{
const float* world_matrix = &xfmem.posMatrices[(MtxIdx & 0x3f) * 4];
@ -662,23 +480,10 @@ void VertexShaderManager::DoState(PointerWrap& p)
p.Do(m_viewport_correction);
g_freelook_camera.DoState(p);
p.DoArray(m_minmax_transform_matrices_changed);
p.DoArray(m_minmax_normal_matrices_changed);
p.DoArray(m_minmax_post_transform_matrices_changed);
p.DoArray(m_minmax_lights_changed);
p.Do(m_materials_changed);
p.DoArray(m_tex_matrices_changed);
p.Do(m_pos_normal_matrix_changed);
p.Do(m_projection_changed);
p.Do(m_viewport_changed);
p.Do(m_tex_mtx_info_changed);
p.Do(m_lighting_config_changed);
p.Do(constants);
if (p.IsReadMode())
{
Dirty();
dirty = true;
}
}

View file

@ -15,28 +15,18 @@
class PointerWrap;
struct PortableVertexDeclaration;
class XFStateManager;
// The non-API dependent parts.
class alignas(16) VertexShaderManager
{
public:
void Init();
void Dirty();
void DoState(PointerWrap& p);
// constant management
void SetProjectionMatrix();
void SetConstants(const std::vector<std::string>& textures);
void InvalidateXFRange(int start, int end);
void SetTexMatrixChangedA(u32 value);
void SetTexMatrixChangedB(u32 value);
void SetViewportChanged();
void SetProjectionChanged();
void SetMaterialColorChanged(int index);
void SetTexMatrixInfoChanged(int index);
void SetLightingConfigChanged();
void SetProjectionMatrix(XFStateManager& xf_state_manager);
void SetConstants(const std::vector<std::string>& textures, XFStateManager& xf_state_manager);
// data: 3 floats representing the X, Y and Z vertex model coordinates and the posmatrix index.
// out: 4 floats which will be initialized with the corresponding clip space coordinates
@ -92,18 +82,7 @@ private:
alignas(16) std::array<float, 16> m_projection_matrix;
// track changes
std::array<bool, 2> m_tex_matrices_changed{};
bool m_pos_normal_matrix_changed = false;
bool m_projection_changed = false;
bool m_viewport_changed = false;
bool m_tex_mtx_info_changed = false;
bool m_lighting_config_changed = false;
bool m_projection_graphics_mod_change = false;
BitSet32 m_materials_changed;
std::array<int, 2> m_minmax_transform_matrices_changed{};
std::array<int, 2> m_minmax_normal_matrices_changed{};
std::array<int, 2> m_minmax_post_transform_matrices_changed{};
std::array<int, 2> m_minmax_lights_changed{};
Common::Matrix44 m_viewport_correction{};

View file

@ -66,6 +66,7 @@
#include "VideoCommon/VideoConfig.h"
#include "VideoCommon/VideoState.h"
#include "VideoCommon/Widescreen.h"
#include "VideoCommon/XFStateManager.h"
VideoBackendBase* g_video_backend = nullptr;
@ -92,7 +93,7 @@ std::string VideoBackendBase::BadShaderFilename(const char* shader_stage, int co
void VideoBackendBase::Video_ExitLoop()
{
auto& system = Core::System::GetInstance();
system.GetFifo().ExitGpuLoop(system);
system.GetFifo().ExitGpuLoop();
}
// Run from the CPU thread (from VideoInterface.cpp)
@ -377,14 +378,15 @@ bool VideoBackendBase::InitializeShared(std::unique_ptr<AbstractGfx> gfx,
auto& system = Core::System::GetInstance();
auto& command_processor = system.GetCommandProcessor();
command_processor.Init(system);
system.GetFifo().Init(system);
system.GetPixelEngine().Init(system);
command_processor.Init();
system.GetFifo().Init();
system.GetPixelEngine().Init();
BPInit();
VertexLoaderManager::Init();
system.GetVertexShaderManager().Init();
system.GetGeometryShaderManager().Init();
system.GetPixelShaderManager().Init();
system.GetXFStateManager().Init();
TMEM::Init();
g_Config.VerifyValidity();

View file

@ -67,13 +67,13 @@ void VideoConfig::Refresh()
const bool lock_gpu_thread = Core::IsRunningAndStarted();
if (lock_gpu_thread)
system.GetFifo().PauseAndLock(system, true, false);
system.GetFifo().PauseAndLock(true, false);
g_Config.Refresh();
g_Config.VerifyValidity();
if (lock_gpu_thread)
system.GetFifo().PauseAndLock(system, false, true);
system.GetFifo().PauseAndLock(false, true);
});
s_has_registered_callback = true;
}
@ -85,6 +85,8 @@ void VideoConfig::Refresh()
bWidescreenHack = Config::Get(Config::GFX_WIDESCREEN_HACK);
aspect_mode = Config::Get(Config::GFX_ASPECT_RATIO);
custom_aspect_width = Config::Get(Config::GFX_CUSTOM_ASPECT_RATIO_WIDTH);
custom_aspect_height = Config::Get(Config::GFX_CUSTOM_ASPECT_RATIO_HEIGHT);
suggested_aspect_mode = Config::Get(Config::GFX_SUGGESTED_ASPECT_RATIO);
widescreen_heuristic_transition_threshold =
Config::Get(Config::GFX_WIDESCREEN_HEURISTIC_TRANSITION_THRESHOLD);
@ -288,6 +290,7 @@ void CheckForConfigChanges()
const u32 old_game_mod_changes =
g_ActiveConfig.graphics_mod_config ? g_ActiveConfig.graphics_mod_config->GetChangeCount() : 0;
const bool old_graphics_mods_enabled = g_ActiveConfig.bGraphicMods;
const AspectMode old_aspect_mode = g_ActiveConfig.aspect_mode;
const AspectMode old_suggested_aspect_mode = g_ActiveConfig.suggested_aspect_mode;
const bool old_widescreen_hack = g_ActiveConfig.bWidescreenHack;
const auto old_post_processing_shader = g_ActiveConfig.sPostProcessingShader;
@ -337,6 +340,8 @@ void CheckForConfigChanges()
changed_bits |= CONFIG_CHANGE_BIT_BBOX;
if (old_efb_scale != g_ActiveConfig.iEFBScale)
changed_bits |= CONFIG_CHANGE_BIT_TARGET_SIZE;
if (old_aspect_mode != g_ActiveConfig.aspect_mode)
changed_bits |= CONFIG_CHANGE_BIT_ASPECT_RATIO;
if (old_suggested_aspect_mode != g_ActiveConfig.suggested_aspect_mode)
changed_bits |= CONFIG_CHANGE_BIT_ASPECT_RATIO;
if (old_widescreen_hack != g_ActiveConfig.bWidescreenHack)
@ -370,6 +375,7 @@ void CheckForConfigChanges()
if (changed_bits & (CONFIG_CHANGE_BIT_HOST_CONFIG | CONFIG_CHANGE_BIT_MULTISAMPLES))
{
OSD::AddMessage("Video config changed, reloading shaders.", OSD::Duration::NORMAL);
g_gfx->WaitForGPUIdle();
g_vertex_manager->InvalidatePipelineObject();
g_vertex_manager->NotifyCustomShaderCacheOfHostChange(new_host_config);
g_shader_cache->SetHostConfig(new_host_config);

View file

@ -21,10 +21,11 @@ constexpr int EFB_SCALE_AUTO_INTEGRAL = 0;
enum class AspectMode : int
{
Auto,
AnalogWide,
Analog,
Auto, // 4:3 or 16:9
ForceWide, // 16:9
ForceStandard, // 4:3
Stretch,
Custom,
};
enum class StereoMode : int
@ -105,6 +106,8 @@ struct VideoConfig final
bool bVSyncActive = false;
bool bWidescreenHack = false;
AspectMode aspect_mode{};
int custom_aspect_width = 1;
int custom_aspect_height = 1;
AspectMode suggested_aspect_mode{};
u32 widescreen_heuristic_transition_threshold = 0;
float widescreen_heuristic_aspect_ratio_slop = 0.f;
@ -142,8 +145,9 @@ struct VideoConfig final
float fSDRDisplayCustomGamma = 2.2f;
// HDR:
// 200 is a good default value that matches the brightness of many SDR screens
float fHDRPaperWhiteNits = 200.f;
// 203 is a good default value that matches the brightness of many SDR screens.
// It's also the value recommended by the ITU.
float fHDRPaperWhiteNits = 203.f;
} color_correction;
// Information
@ -365,6 +369,8 @@ struct VideoConfig final
bool UsingUberShaders() const;
u32 GetShaderCompilerThreads() const;
u32 GetShaderPrecompilerThreads() const;
float GetCustomAspectRatio() const { return (float)custom_aspect_width / custom_aspect_height; }
};
extern VideoConfig g_Config;

View file

@ -81,3 +81,6 @@ using BeforePresentEvent = Common::HookableEvent<"BeforePresent", PresentInfo&>;
// An event that is triggered after a frame is presented.
// The exact timing of this event depends on backend/driver support.
using AfterPresentEvent = Common::HookableEvent<"AfterPresent", PresentInfo&>;
// An end of frame event that runs on the CPU thread
using VIEndFieldEvent = Common::HookableEvent<"VIEndField">;

View file

@ -27,6 +27,7 @@
#include "VideoCommon/VertexShaderManager.h"
#include "VideoCommon/Widescreen.h"
#include "VideoCommon/XFMemory.h"
#include "VideoCommon/XFStateManager.h"
void VideoCommon_DoState(PointerWrap& p)
{
@ -105,6 +106,9 @@ void VideoCommon_DoState(PointerWrap& p)
g_widescreen->DoState(p);
p.DoMarker("Widescreen");
system.GetXFStateManager().DoState(p);
p.DoMarker("XFStateManager");
// Refresh state.
if (p.IsReadMode())
{

View file

@ -36,23 +36,24 @@ void WidescreenManager::Update()
m_is_game_widescreen = Config::Get(Config::SYSCONF_WIDESCREEN);
// suggested_aspect_mode overrides SYSCONF_WIDESCREEN
if (g_ActiveConfig.suggested_aspect_mode == AspectMode::Analog)
if (g_ActiveConfig.suggested_aspect_mode == AspectMode::ForceStandard)
m_is_game_widescreen = false;
else if (g_ActiveConfig.suggested_aspect_mode == AspectMode::AnalogWide)
else if (g_ActiveConfig.suggested_aspect_mode == AspectMode::ForceWide)
m_is_game_widescreen = true;
// If widescreen hack is disabled override game's AR if UI is set to 4:3 or 16:9.
if (!g_ActiveConfig.bWidescreenHack)
{
const auto aspect_mode = g_ActiveConfig.aspect_mode;
if (aspect_mode == AspectMode::Analog)
if (aspect_mode == AspectMode::ForceStandard)
m_is_game_widescreen = false;
else if (aspect_mode == AspectMode::AnalogWide)
else if (aspect_mode == AspectMode::ForceWide)
m_is_game_widescreen = true;
}
}
// Heuristic to detect if a GameCube game is in 16:9 anamorphic widescreen mode.
// Cheats that change the game aspect ratio to natively unsupported ones won't be recognized here.
void WidescreenManager::UpdateWidescreenHeuristic()
{
const auto flush_statistics = g_vertex_manager->ResetFlushAspectRatioCount();
@ -63,9 +64,10 @@ void WidescreenManager::UpdateWidescreenHeuristic()
Update();
// If widescreen hack isn't active and aspect_mode (UI) is 4:3 or 16:9 don't use heuristic.
if (!g_ActiveConfig.bWidescreenHack && (g_ActiveConfig.aspect_mode == AspectMode::Analog ||
g_ActiveConfig.aspect_mode == AspectMode::AnalogWide))
// If widescreen hack isn't active and aspect_mode (user setting)
// is set to a forced aspect ratio, don't use heuristic.
if (!g_ActiveConfig.bWidescreenHack && (g_ActiveConfig.aspect_mode == AspectMode::ForceStandard ||
g_ActiveConfig.aspect_mode == AspectMode::ForceWide))
return;
// Modify the threshold based on which aspect ratio we're already using:

View file

@ -11,11 +11,14 @@
class PointerWrap;
// This class is responsible for tracking the game's aspect ratio.
// This exclusively supports 4:3 or 16:9 detection by default.
class WidescreenManager
{
public:
WidescreenManager();
// Just a helper to tell whether the game seems to be running in widescreen,
// or if it's being forced to.
bool IsGameWidescreen() const { return m_is_game_widescreen; }
void DoState(PointerWrap& p);

View file

@ -0,0 +1,279 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "VideoCommon/XFStateManager.h"
#include "Common/ChunkFile.h"
#include "VideoCommon/FramebufferManager.h"
#include "VideoCommon/VertexManagerBase.h"
#include "VideoCommon/XFMemory.h"
void XFStateManager::Init()
{
// Initialize state tracking variables
ResetTexMatrixAChange();
ResetTexMatrixBChange();
ResetPosNormalChange();
ResetProjection();
ResetViewportChange();
ResetTexMatrixInfoChange();
ResetLightingConfigChange();
ResetLightsChanged();
ResetMaterialChanges();
ResetPerVertexTransformMatrixChanges();
ResetPerVertexNormalMatrixChanges();
ResetPostTransformMatrixChanges();
std::memset(static_cast<void*>(&xfmem), 0, sizeof(xfmem));
}
void XFStateManager::DoState(PointerWrap& p)
{
p.DoArray(m_minmax_transform_matrices_changed);
p.DoArray(m_minmax_normal_matrices_changed);
p.DoArray(m_minmax_post_transform_matrices_changed);
p.DoArray(m_minmax_lights_changed);
p.Do(m_materials_changed);
p.DoArray(m_tex_matrices_changed);
p.Do(m_pos_normal_matrix_changed);
p.Do(m_projection_changed);
p.Do(m_viewport_changed);
p.Do(m_tex_mtx_info_changed);
p.Do(m_lighting_config_changed);
if (p.IsReadMode())
{
// This is called after a savestate is loaded.
// Any constants that can changed based on settings should be re-calculated
m_projection_changed = true;
}
}
void XFStateManager::InvalidateXFRange(int start, int end)
{
if (((u32)start >= (u32)g_main_cp_state.matrix_index_a.PosNormalMtxIdx * 4 &&
(u32)start < (u32)g_main_cp_state.matrix_index_a.PosNormalMtxIdx * 4 + 12) ||
((u32)start >=
XFMEM_NORMALMATRICES + ((u32)g_main_cp_state.matrix_index_a.PosNormalMtxIdx & 31) * 3 &&
(u32)start < XFMEM_NORMALMATRICES +
((u32)g_main_cp_state.matrix_index_a.PosNormalMtxIdx & 31) * 3 + 9))
{
m_pos_normal_matrix_changed = true;
}
if (((u32)start >= (u32)g_main_cp_state.matrix_index_a.Tex0MtxIdx * 4 &&
(u32)start < (u32)g_main_cp_state.matrix_index_a.Tex0MtxIdx * 4 + 12) ||
((u32)start >= (u32)g_main_cp_state.matrix_index_a.Tex1MtxIdx * 4 &&
(u32)start < (u32)g_main_cp_state.matrix_index_a.Tex1MtxIdx * 4 + 12) ||
((u32)start >= (u32)g_main_cp_state.matrix_index_a.Tex2MtxIdx * 4 &&
(u32)start < (u32)g_main_cp_state.matrix_index_a.Tex2MtxIdx * 4 + 12) ||
((u32)start >= (u32)g_main_cp_state.matrix_index_a.Tex3MtxIdx * 4 &&
(u32)start < (u32)g_main_cp_state.matrix_index_a.Tex3MtxIdx * 4 + 12))
{
m_tex_matrices_changed[0] = true;
}
if (((u32)start >= (u32)g_main_cp_state.matrix_index_b.Tex4MtxIdx * 4 &&
(u32)start < (u32)g_main_cp_state.matrix_index_b.Tex4MtxIdx * 4 + 12) ||
((u32)start >= (u32)g_main_cp_state.matrix_index_b.Tex5MtxIdx * 4 &&
(u32)start < (u32)g_main_cp_state.matrix_index_b.Tex5MtxIdx * 4 + 12) ||
((u32)start >= (u32)g_main_cp_state.matrix_index_b.Tex6MtxIdx * 4 &&
(u32)start < (u32)g_main_cp_state.matrix_index_b.Tex6MtxIdx * 4 + 12) ||
((u32)start >= (u32)g_main_cp_state.matrix_index_b.Tex7MtxIdx * 4 &&
(u32)start < (u32)g_main_cp_state.matrix_index_b.Tex7MtxIdx * 4 + 12))
{
m_tex_matrices_changed[1] = true;
}
if (start < XFMEM_POSMATRICES_END)
{
if (m_minmax_transform_matrices_changed[0] == -1)
{
m_minmax_transform_matrices_changed[0] = start;
m_minmax_transform_matrices_changed[1] =
end > XFMEM_POSMATRICES_END ? XFMEM_POSMATRICES_END : end;
}
else
{
if (m_minmax_transform_matrices_changed[0] > start)
m_minmax_transform_matrices_changed[0] = start;
if (m_minmax_transform_matrices_changed[1] < end)
m_minmax_transform_matrices_changed[1] =
end > XFMEM_POSMATRICES_END ? XFMEM_POSMATRICES_END : end;
}
}
if (start < XFMEM_NORMALMATRICES_END && end > XFMEM_NORMALMATRICES)
{
int _start = start < XFMEM_NORMALMATRICES ? 0 : start - XFMEM_NORMALMATRICES;
int _end = end < XFMEM_NORMALMATRICES_END ? end - XFMEM_NORMALMATRICES :
XFMEM_NORMALMATRICES_END - XFMEM_NORMALMATRICES;
if (m_minmax_normal_matrices_changed[0] == -1)
{
m_minmax_normal_matrices_changed[0] = _start;
m_minmax_normal_matrices_changed[1] = _end;
}
else
{
if (m_minmax_normal_matrices_changed[0] > _start)
m_minmax_normal_matrices_changed[0] = _start;
if (m_minmax_normal_matrices_changed[1] < _end)
m_minmax_normal_matrices_changed[1] = _end;
}
}
if (start < XFMEM_POSTMATRICES_END && end > XFMEM_POSTMATRICES)
{
int _start = start < XFMEM_POSTMATRICES ? XFMEM_POSTMATRICES : start - XFMEM_POSTMATRICES;
int _end = end < XFMEM_POSTMATRICES_END ? end - XFMEM_POSTMATRICES :
XFMEM_POSTMATRICES_END - XFMEM_POSTMATRICES;
if (m_minmax_post_transform_matrices_changed[0] == -1)
{
m_minmax_post_transform_matrices_changed[0] = _start;
m_minmax_post_transform_matrices_changed[1] = _end;
}
else
{
if (m_minmax_post_transform_matrices_changed[0] > _start)
m_minmax_post_transform_matrices_changed[0] = _start;
if (m_minmax_post_transform_matrices_changed[1] < _end)
m_minmax_post_transform_matrices_changed[1] = _end;
}
}
if (start < XFMEM_LIGHTS_END && end > XFMEM_LIGHTS)
{
int _start = start < XFMEM_LIGHTS ? XFMEM_LIGHTS : start - XFMEM_LIGHTS;
int _end = end < XFMEM_LIGHTS_END ? end - XFMEM_LIGHTS : XFMEM_LIGHTS_END - XFMEM_LIGHTS;
if (m_minmax_lights_changed[0] == -1)
{
m_minmax_lights_changed[0] = _start;
m_minmax_lights_changed[1] = _end;
}
else
{
if (m_minmax_lights_changed[0] > _start)
m_minmax_lights_changed[0] = _start;
if (m_minmax_lights_changed[1] < _end)
m_minmax_lights_changed[1] = _end;
}
}
}
void XFStateManager::SetTexMatrixChangedA(u32 Value)
{
if (g_main_cp_state.matrix_index_a.Hex != Value)
{
g_vertex_manager->Flush();
if (g_main_cp_state.matrix_index_a.PosNormalMtxIdx != (Value & 0x3f))
m_pos_normal_matrix_changed = true;
m_tex_matrices_changed[0] = true;
g_main_cp_state.matrix_index_a.Hex = Value;
}
}
void XFStateManager::ResetTexMatrixAChange()
{
m_tex_matrices_changed[0] = false;
}
void XFStateManager::SetTexMatrixChangedB(u32 Value)
{
if (g_main_cp_state.matrix_index_b.Hex != Value)
{
g_vertex_manager->Flush();
m_tex_matrices_changed[1] = true;
g_main_cp_state.matrix_index_b.Hex = Value;
}
}
void XFStateManager::ResetTexMatrixBChange()
{
m_tex_matrices_changed[1] = false;
}
void XFStateManager::ResetPosNormalChange()
{
m_pos_normal_matrix_changed = false;
}
void XFStateManager::SetProjectionChanged()
{
m_projection_changed = true;
}
void XFStateManager::ResetProjection()
{
m_projection_changed = false;
}
void XFStateManager::SetViewportChanged()
{
m_viewport_changed = true;
}
void XFStateManager::ResetViewportChange()
{
m_viewport_changed = false;
}
void XFStateManager::SetTexMatrixInfoChanged(int index)
{
// TODO: Should we track this with more precision, like which indices changed?
// The whole vertex constants are probably going to be uploaded regardless.
m_tex_mtx_info_changed = true;
}
void XFStateManager::ResetTexMatrixInfoChange()
{
m_tex_mtx_info_changed = false;
}
void XFStateManager::SetLightingConfigChanged()
{
m_lighting_config_changed = true;
}
void XFStateManager::ResetLightingConfigChange()
{
m_lighting_config_changed = false;
}
void XFStateManager::ResetLightsChanged()
{
m_minmax_lights_changed.fill(-1);
}
void XFStateManager::SetMaterialColorChanged(int index)
{
m_materials_changed[index] = true;
}
void XFStateManager::ResetMaterialChanges()
{
m_materials_changed = BitSet32(0);
}
void XFStateManager::ResetPerVertexTransformMatrixChanges()
{
m_minmax_transform_matrices_changed.fill(-1);
}
void XFStateManager::ResetPerVertexNormalMatrixChanges()
{
m_minmax_normal_matrices_changed.fill(-1);
}
void XFStateManager::ResetPostTransformMatrixChanges()
{
m_minmax_post_transform_matrices_changed.fill(-1);
}

View file

@ -0,0 +1,87 @@
// Copyright 2023 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#include "Common/BitSet.h"
class PointerWrap;
// This class manages how XF state changes over
// a period of time (typically a single draw call)
class XFStateManager
{
public:
void Init();
void DoState(PointerWrap& p);
void InvalidateXFRange(int start, int end);
void SetTexMatrixChangedA(u32 value);
bool DidTexMatrixAChange() const { return m_tex_matrices_changed[0]; }
void ResetTexMatrixAChange();
void SetTexMatrixChangedB(u32 value);
bool DidTexMatrixBChange() const { return m_tex_matrices_changed[1]; }
void ResetTexMatrixBChange();
bool DidPosNormalChange() const { return m_pos_normal_matrix_changed; }
void ResetPosNormalChange();
void SetProjectionChanged();
bool DidProjectionChange() const { return m_projection_changed; }
void ResetProjection();
void SetViewportChanged();
bool DidViewportChange() const { return m_viewport_changed; }
void ResetViewportChange();
void SetTexMatrixInfoChanged(int index);
bool DidTexMatrixInfoChange() const { return m_tex_mtx_info_changed; }
void ResetTexMatrixInfoChange();
void SetLightingConfigChanged();
bool DidLightingConfigChange() const { return m_lighting_config_changed; }
void ResetLightingConfigChange();
const std::array<int, 2>& GetLightsChanged() const { return m_minmax_lights_changed; }
void ResetLightsChanged();
void SetMaterialColorChanged(int index);
const BitSet32& GetMaterialChanges() const { return m_materials_changed; }
void ResetMaterialChanges();
const std::array<int, 2>& GetPerVertexTransformMatrixChanges() const
{
return m_minmax_transform_matrices_changed;
}
void ResetPerVertexTransformMatrixChanges();
const std::array<int, 2>& GetPerVertexNormalMatrixChanges() const
{
return m_minmax_normal_matrices_changed;
}
void ResetPerVertexNormalMatrixChanges();
const std::array<int, 2>& GetPostTransformMatrixChanges() const
{
return m_minmax_post_transform_matrices_changed;
}
void ResetPostTransformMatrixChanges();
private:
// track changes
std::array<bool, 2> m_tex_matrices_changed{};
bool m_pos_normal_matrix_changed = false;
bool m_projection_changed = false;
bool m_viewport_changed = false;
bool m_tex_mtx_info_changed = false;
bool m_lighting_config_changed = false;
BitSet32 m_materials_changed;
std::array<int, 2> m_minmax_transform_matrices_changed{};
std::array<int, 2> m_minmax_normal_matrices_changed{};
std::array<int, 2> m_minmax_post_transform_matrices_changed{};
std::array<int, 2> m_minmax_lights_changed{};
};

View file

@ -18,18 +18,17 @@
#include "VideoCommon/PixelShaderManager.h"
#include "VideoCommon/VertexLoaderManager.h"
#include "VideoCommon/VertexManagerBase.h"
#include "VideoCommon/VertexShaderManager.h"
#include "VideoCommon/XFMemory.h"
#include "VideoCommon/XFStateManager.h"
static void XFMemWritten(VertexShaderManager& vertex_shader_manager, u32 transferSize,
u32 baseAddress)
static void XFMemWritten(XFStateManager& xf_state_manager, u32 transferSize, u32 baseAddress)
{
g_vertex_manager->Flush();
vertex_shader_manager.InvalidateXFRange(baseAddress, baseAddress + transferSize);
xf_state_manager.InvalidateXFRange(baseAddress, baseAddress + transferSize);
}
static void XFRegWritten(Core::System& system, VertexShaderManager& vertex_shader_manager,
u32 address, u32 value)
static void XFRegWritten(Core::System& system, XFStateManager& xf_state_manager, u32 address,
u32 value)
{
if (address >= XFMEM_REGISTERS_START && address < XFMEM_REGISTERS_END)
{
@ -63,7 +62,7 @@ static void XFRegWritten(Core::System& system, VertexShaderManager& vertex_shade
case XFMEM_SETNUMCHAN:
if (xfmem.numChan.numColorChans != (value & 3))
g_vertex_manager->Flush();
vertex_shader_manager.SetLightingConfigChanged();
xf_state_manager.SetLightingConfigChanged();
break;
case XFMEM_SETCHAN0_AMBCOLOR: // Channel Ambient Color
@ -73,7 +72,7 @@ static void XFRegWritten(Core::System& system, VertexShaderManager& vertex_shade
if (xfmem.ambColor[chan] != value)
{
g_vertex_manager->Flush();
vertex_shader_manager.SetMaterialColorChanged(chan);
xf_state_manager.SetMaterialColorChanged(chan);
}
break;
}
@ -85,7 +84,7 @@ static void XFRegWritten(Core::System& system, VertexShaderManager& vertex_shade
if (xfmem.matColor[chan] != value)
{
g_vertex_manager->Flush();
vertex_shader_manager.SetMaterialColorChanged(chan + 2);
xf_state_manager.SetMaterialColorChanged(chan + 2);
}
break;
}
@ -96,21 +95,21 @@ static void XFRegWritten(Core::System& system, VertexShaderManager& vertex_shade
case XFMEM_SETCHAN1_ALPHA:
if (((u32*)&xfmem)[address] != (value & 0x7fff))
g_vertex_manager->Flush();
vertex_shader_manager.SetLightingConfigChanged();
xf_state_manager.SetLightingConfigChanged();
break;
case XFMEM_DUALTEX:
if (xfmem.dualTexTrans.enabled != bool(value & 1))
g_vertex_manager->Flush();
vertex_shader_manager.SetTexMatrixInfoChanged(-1);
xf_state_manager.SetTexMatrixInfoChanged(-1);
break;
case XFMEM_SETMATRIXINDA:
vertex_shader_manager.SetTexMatrixChangedA(value);
xf_state_manager.SetTexMatrixChangedA(value);
VertexLoaderManager::g_needs_cp_xf_consistency_check = true;
break;
case XFMEM_SETMATRIXINDB:
vertex_shader_manager.SetTexMatrixChangedB(value);
xf_state_manager.SetTexMatrixChangedB(value);
VertexLoaderManager::g_needs_cp_xf_consistency_check = true;
break;
@ -121,7 +120,7 @@ static void XFRegWritten(Core::System& system, VertexShaderManager& vertex_shade
case XFMEM_SETVIEWPORT + 4:
case XFMEM_SETVIEWPORT + 5:
g_vertex_manager->Flush();
vertex_shader_manager.SetViewportChanged();
xf_state_manager.SetViewportChanged();
system.GetPixelShaderManager().SetViewportChanged();
system.GetGeometryShaderManager().SetViewportChanged();
break;
@ -134,7 +133,7 @@ static void XFRegWritten(Core::System& system, VertexShaderManager& vertex_shade
case XFMEM_SETPROJECTION + 5:
case XFMEM_SETPROJECTION + 6:
g_vertex_manager->Flush();
vertex_shader_manager.SetProjectionChanged();
xf_state_manager.SetProjectionChanged();
system.GetGeometryShaderManager().SetProjectionChanged();
break;
@ -152,7 +151,7 @@ static void XFRegWritten(Core::System& system, VertexShaderManager& vertex_shade
case XFMEM_SETTEXMTXINFO + 6:
case XFMEM_SETTEXMTXINFO + 7:
g_vertex_manager->Flush();
vertex_shader_manager.SetTexMatrixInfoChanged(address - XFMEM_SETTEXMTXINFO);
xf_state_manager.SetTexMatrixInfoChanged(address - XFMEM_SETTEXMTXINFO);
break;
case XFMEM_SETPOSTMTXINFO:
@ -164,7 +163,7 @@ static void XFRegWritten(Core::System& system, VertexShaderManager& vertex_shade
case XFMEM_SETPOSTMTXINFO + 6:
case XFMEM_SETPOSTMTXINFO + 7:
g_vertex_manager->Flush();
vertex_shader_manager.SetTexMatrixInfoChanged(address - XFMEM_SETPOSTMTXINFO);
xf_state_manager.SetTexMatrixInfoChanged(address - XFMEM_SETPOSTMTXINFO);
break;
// --------------
@ -218,7 +217,7 @@ void LoadXFReg(u16 base_address, u8 transfer_size, const u8* data)
}
auto& system = Core::System::GetInstance();
auto& vertex_shader_manager = system.GetVertexShaderManager();
auto& xf_state_manager = system.GetXFStateManager();
// write to XF mem
if (base_address < XFMEM_REGISTERS_START)
@ -232,7 +231,7 @@ void LoadXFReg(u16 base_address, u8 transfer_size, const u8* data)
base_address = XFMEM_REGISTERS_START;
}
XFMemWritten(vertex_shader_manager, xf_mem_transfer_size, xf_mem_base);
XFMemWritten(xf_state_manager, xf_mem_transfer_size, xf_mem_base);
for (u32 i = 0; i < xf_mem_transfer_size; i++)
{
((u32*)&xfmem)[xf_mem_base + i] = Common::swap32(data);
@ -247,7 +246,7 @@ void LoadXFReg(u16 base_address, u8 transfer_size, const u8* data)
{
const u32 value = Common::swap32(data);
XFRegWritten(system, vertex_shader_manager, address, value);
XFRegWritten(system, xf_state_manager, address, value);
((u32*)&xfmem)[address] = value;
data += 4;
@ -275,14 +274,14 @@ void LoadIndexedXF(CPArray array, u32 index, u16 address, u8 size)
g_main_cp_state.array_strides[array] * index);
}
auto& vertex_shader_manager = system.GetVertexShaderManager();
auto& xf_state_manager = system.GetXFStateManager();
bool changed = false;
for (u32 i = 0; i < size; ++i)
{
if (currData[i] != Common::swap32(newData[i]))
{
changed = true;
XFMemWritten(vertex_shader_manager, size, address);
XFMemWritten(xf_state_manager, size, address);
break;
}
}