mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-08-30 14:18:44 +00:00
Merge pull request #20 from dolphin-emu/master
[pull] master from dolphin-emu:master
This commit is contained in:
commit
872ece4c2f
92 changed files with 18567 additions and 2615 deletions
|
@ -20,14 +20,7 @@ struct PixelShaderData;
|
|||
class CustomAssetLibrary
|
||||
{
|
||||
public:
|
||||
// TODO: this should be std::chrono::system_clock::time_point to
|
||||
// support any type of loader where the time isn't from the filesystem
|
||||
// but there's no way to convert filesystem times to system times
|
||||
// without 'clock_cast', once our builders catch up
|
||||
// to support 'clock_cast' we should update this
|
||||
// For now, it's fine as a filesystem library is all that is
|
||||
// available
|
||||
using TimeType = std::filesystem::file_time_type;
|
||||
using TimeType = std::chrono::system_clock::time_point;
|
||||
|
||||
// The AssetID is a unique identifier for a particular asset
|
||||
using AssetID = std::string;
|
||||
|
|
|
@ -17,6 +17,20 @@ namespace VideoCommon
|
|||
{
|
||||
namespace
|
||||
{
|
||||
std::chrono::system_clock::time_point FileTimeToSysTime(std::filesystem::file_time_type file_time)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return std::chrono::clock_cast<std::chrono::system_clock>(file_time);
|
||||
#else
|
||||
// Note: all compilers should switch to chrono::clock_cast
|
||||
// once it is available for use
|
||||
const auto system_time_now = std::chrono::system_clock::now();
|
||||
const auto file_time_now = decltype(file_time)::clock::now();
|
||||
return std::chrono::time_point_cast<std::chrono::system_clock::duration>(
|
||||
file_time - file_time_now + system_time_now);
|
||||
#endif
|
||||
}
|
||||
|
||||
std::size_t GetAssetSize(const CustomTextureData& data)
|
||||
{
|
||||
std::size_t total = 0;
|
||||
|
@ -42,8 +56,9 @@ DirectFilesystemAssetLibrary::GetLastAssetWriteTime(const AssetID& asset_id) con
|
|||
const auto tp = std::filesystem::last_write_time(value, ec);
|
||||
if (ec)
|
||||
continue;
|
||||
if (tp > max_entry)
|
||||
max_entry = tp;
|
||||
auto tp_sys = FileTimeToSysTime(tp);
|
||||
if (tp_sys > max_entry)
|
||||
max_entry = tp_sys;
|
||||
}
|
||||
return max_entry;
|
||||
}
|
||||
|
@ -235,7 +250,7 @@ CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadTexture(const Ass
|
|||
if (!LoadMips(asset_path, data))
|
||||
return {};
|
||||
|
||||
return LoadInfo{GetAssetSize(*data), last_loaded_time};
|
||||
return LoadInfo{GetAssetSize(*data), FileTimeToSysTime(last_loaded_time)};
|
||||
}
|
||||
else if (ext == ".png")
|
||||
{
|
||||
|
@ -252,7 +267,7 @@ CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadTexture(const Ass
|
|||
if (!LoadMips(asset_path, data))
|
||||
return {};
|
||||
|
||||
return LoadInfo{GetAssetSize(*data), last_loaded_time};
|
||||
return LoadInfo{GetAssetSize(*data), FileTimeToSysTime(last_loaded_time)};
|
||||
}
|
||||
|
||||
ERROR_LOG_FMT(VIDEO, "Asset '{}' error - extension '{}' unknown!", asset_id, ext);
|
||||
|
|
|
@ -64,6 +64,8 @@ add_library(videocommon
|
|||
GeometryShaderManager.h
|
||||
GraphicsModSystem/Config/GraphicsMod.cpp
|
||||
GraphicsModSystem/Config/GraphicsMod.h
|
||||
GraphicsModSystem/Config/GraphicsModAsset.cpp
|
||||
GraphicsModSystem/Config/GraphicsModAsset.h
|
||||
GraphicsModSystem/Config/GraphicsModFeature.cpp
|
||||
GraphicsModSystem/Config/GraphicsModFeature.h
|
||||
GraphicsModSystem/Config/GraphicsModGroup.cpp
|
||||
|
@ -73,6 +75,8 @@ add_library(videocommon
|
|||
GraphicsModSystem/Config/GraphicsTargetGroup.cpp
|
||||
GraphicsModSystem/Config/GraphicsTargetGroup.h
|
||||
GraphicsModSystem/Constants.h
|
||||
GraphicsModSystem/Runtime/Actions/CustomPipelineAction.cpp
|
||||
GraphicsModSystem/Runtime/Actions/CustomPipelineAction.h
|
||||
GraphicsModSystem/Runtime/Actions/MoveAction.cpp
|
||||
GraphicsModSystem/Runtime/Actions/MoveAction.h
|
||||
GraphicsModSystem/Runtime/Actions/PrintAction.cpp
|
||||
|
@ -81,6 +85,8 @@ add_library(videocommon
|
|||
GraphicsModSystem/Runtime/Actions/ScaleAction.h
|
||||
GraphicsModSystem/Runtime/Actions/SkipAction.cpp
|
||||
GraphicsModSystem/Runtime/Actions/SkipAction.h
|
||||
GraphicsModSystem/Runtime/CustomShaderCache.cpp
|
||||
GraphicsModSystem/Runtime/CustomShaderCache.h
|
||||
GraphicsModSystem/Runtime/FBInfo.cpp
|
||||
GraphicsModSystem/Runtime/FBInfo.h
|
||||
GraphicsModSystem/Runtime/GraphicsModAction.h
|
||||
|
|
|
@ -58,6 +58,8 @@ struct alignas(16) PixelShaderConstants
|
|||
// For shader_framebuffer_fetch logic ops:
|
||||
u32 logic_op_enable; // bool
|
||||
LogicOp logic_op_mode;
|
||||
// For custom shaders...
|
||||
u32 time_ms;
|
||||
};
|
||||
|
||||
struct alignas(16) VertexShaderConstants
|
||||
|
|
|
@ -178,6 +178,27 @@ bool GraphicsModConfig::DeserializeFromConfig(const picojson::value& value)
|
|||
}
|
||||
}
|
||||
|
||||
const auto& assets = value.get("assets");
|
||||
if (assets.is<picojson::array>())
|
||||
{
|
||||
for (const auto& asset_val : assets.get<picojson::array>())
|
||||
{
|
||||
if (!asset_val.is<picojson::object>())
|
||||
{
|
||||
ERROR_LOG_FMT(
|
||||
VIDEO, "Failed to load mod configuration file, specified asset is not a json object");
|
||||
return false;
|
||||
}
|
||||
GraphicsModAssetConfig asset;
|
||||
if (!asset.DeserializeFromConfig(asset_val.get<picojson::object>()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
m_assets.push_back(std::move(asset));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
#include <picojson.h>
|
||||
|
||||
#include "VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Config/GraphicsModFeature.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Config/GraphicsTargetGroup.h"
|
||||
|
||||
|
@ -30,6 +31,7 @@ struct GraphicsModConfig
|
|||
|
||||
std::vector<GraphicsTargetGroupConfig> m_groups;
|
||||
std::vector<GraphicsModFeatureConfig> m_features;
|
||||
std::vector<GraphicsModAssetConfig> m_assets;
|
||||
|
||||
static std::optional<GraphicsModConfig> Create(const std::string& file, Source source);
|
||||
static std::optional<GraphicsModConfig> Create(const picojson::object* obj);
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
// Copyright 2023 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.h"
|
||||
|
||||
#include "Common/Logging/Log.h"
|
||||
|
||||
bool GraphicsModAssetConfig::DeserializeFromConfig(const picojson::object& obj)
|
||||
{
|
||||
auto name_iter = obj.find("name");
|
||||
if (name_iter == obj.end())
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, specified asset has no name");
|
||||
return false;
|
||||
}
|
||||
if (!name_iter->second.is<std::string>())
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, specified asset has a name "
|
||||
"that is not a string");
|
||||
return false;
|
||||
}
|
||||
m_name = name_iter->second.to_str();
|
||||
|
||||
auto data_iter = obj.find("data");
|
||||
if (data_iter == obj.end())
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Failed to load mod configuration file, specified asset '{}' has no data",
|
||||
m_name);
|
||||
return false;
|
||||
}
|
||||
if (!data_iter->second.is<picojson::object>())
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO,
|
||||
"Failed to load mod configuration file, specified asset '{}' has data "
|
||||
"that is not an object",
|
||||
m_name);
|
||||
return false;
|
||||
}
|
||||
for (const auto& [key, value] : data_iter->second.get<picojson::object>())
|
||||
{
|
||||
if (!value.is<std::string>())
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO,
|
||||
"Failed to load mod configuration file, specified asset '{}' has data "
|
||||
"with a value for key '{}' that is not a string",
|
||||
m_name, key);
|
||||
return false;
|
||||
}
|
||||
m_map[key] = value.to_str();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
// Copyright 2023 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
#include <picojson.h>
|
||||
|
||||
#include "VideoCommon/Assets/DirectFilesystemAssetLibrary.h"
|
||||
|
||||
struct GraphicsModAssetConfig
|
||||
{
|
||||
std::string m_name;
|
||||
VideoCommon::DirectFilesystemAssetLibrary::AssetMap m_map;
|
||||
|
||||
bool DeserializeFromConfig(const picojson::object& obj);
|
||||
};
|
|
@ -0,0 +1,449 @@
|
|||
// Copyright 2022 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "Common/FileUtil.h"
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/StringUtil.h"
|
||||
#include "Core/System.h"
|
||||
|
||||
#include "VideoCommon/AbstractGfx.h"
|
||||
#include "VideoCommon/Assets/CustomAssetLoader.h"
|
||||
#include "VideoCommon/Assets/DirectFilesystemAssetLibrary.h"
|
||||
#include "VideoCommon/ShaderGenCommon.h"
|
||||
#include "VideoCommon/TextureCacheBase.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
bool IsQualifier(std::string_view value)
|
||||
{
|
||||
static std::array<std::string_view, 7> qualifiers = {"attribute", "const", "highp", "lowp",
|
||||
"mediump", "uniform", "varying"};
|
||||
return std::find(qualifiers.begin(), qualifiers.end(), value) != qualifiers.end();
|
||||
}
|
||||
|
||||
bool IsBuiltInMacro(std::string_view value)
|
||||
{
|
||||
static std::array<std::string_view, 5> built_in = {"__LINE__", "__FILE__", "__VERSION__",
|
||||
"GL_core_profile", "GL_compatibility_profile"};
|
||||
return std::find(built_in.begin(), built_in.end(), value) != built_in.end();
|
||||
}
|
||||
|
||||
std::vector<std::string> GlobalConflicts(std::string_view source)
|
||||
{
|
||||
std::string_view last_identifier = "";
|
||||
std::vector<std::string> global_result;
|
||||
u32 scope = 0;
|
||||
for (u32 i = 0; i < source.size(); i++)
|
||||
{
|
||||
// If we're out of global scope, we don't care
|
||||
// about any of the details
|
||||
if (scope > 0)
|
||||
{
|
||||
if (source[i] == '{')
|
||||
{
|
||||
scope++;
|
||||
}
|
||||
else if (source[i] == '}')
|
||||
{
|
||||
scope--;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto parse_identifier = [&]() {
|
||||
const u32 start = i;
|
||||
for (; i < source.size(); i++)
|
||||
{
|
||||
if (!Common::IsAlpha(source[i]) && source[i] != '_' && !std::isdigit(source[i]))
|
||||
break;
|
||||
}
|
||||
u32 end = i;
|
||||
i--; // unwind
|
||||
return source.substr(start, end - start);
|
||||
};
|
||||
|
||||
if (Common::IsAlpha(source[i]) || source[i] == '_')
|
||||
{
|
||||
const std::string_view identifier = parse_identifier();
|
||||
if (IsQualifier(identifier))
|
||||
continue;
|
||||
if (IsBuiltInMacro(identifier))
|
||||
continue;
|
||||
last_identifier = identifier;
|
||||
}
|
||||
else if (source[i] == '#')
|
||||
{
|
||||
const auto parse_until_end_of_preprocessor = [&]() {
|
||||
bool continue_until_next_newline = false;
|
||||
for (; i < source.size(); i++)
|
||||
{
|
||||
if (source[i] == '\n')
|
||||
{
|
||||
if (continue_until_next_newline)
|
||||
continue_until_next_newline = false;
|
||||
else
|
||||
break;
|
||||
}
|
||||
else if (source[i] == '\\')
|
||||
{
|
||||
continue_until_next_newline = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
i++;
|
||||
const std::string_view identifier = parse_identifier();
|
||||
if (identifier == "define")
|
||||
{
|
||||
i++;
|
||||
// skip whitespace
|
||||
while (source[i] == ' ')
|
||||
{
|
||||
i++;
|
||||
}
|
||||
global_result.push_back(std::string{parse_identifier()});
|
||||
parse_until_end_of_preprocessor();
|
||||
}
|
||||
else
|
||||
{
|
||||
parse_until_end_of_preprocessor();
|
||||
}
|
||||
}
|
||||
else if (source[i] == '{')
|
||||
{
|
||||
scope++;
|
||||
}
|
||||
else if (source[i] == '(')
|
||||
{
|
||||
// Unlikely the user will be using layouts but...
|
||||
if (last_identifier == "layout")
|
||||
continue;
|
||||
|
||||
// Since we handle equality, we can assume the identifier
|
||||
// before '(' is a function definition
|
||||
global_result.push_back(std::string{last_identifier});
|
||||
}
|
||||
else if (source[i] == '=')
|
||||
{
|
||||
global_result.push_back(std::string{last_identifier});
|
||||
i++;
|
||||
for (; i < source.size(); i++)
|
||||
{
|
||||
if (source[i] == ';')
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (source[i] == '/')
|
||||
{
|
||||
if ((i + 1) >= source.size())
|
||||
continue;
|
||||
|
||||
if (source[i + 1] == '/')
|
||||
{
|
||||
// Go to end of line...
|
||||
for (; i < source.size(); i++)
|
||||
{
|
||||
if (source[i] == '\n')
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (source[i + 1] == '*')
|
||||
{
|
||||
// Multiline, look for first '*/'
|
||||
for (; i < source.size(); i++)
|
||||
{
|
||||
if (source[i] == '/' && source[i - 1] == '*')
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the conflicts from largest to smallest string
|
||||
// this way we can ensure smaller strings that are a substring
|
||||
// of the larger string are able to be replaced appropriately
|
||||
std::sort(global_result.begin(), global_result.end(),
|
||||
[](const std::string& first, const std::string& second) {
|
||||
return first.size() > second.size();
|
||||
});
|
||||
return global_result;
|
||||
}
|
||||
|
||||
void WriteDefines(ShaderCode* out, const std::vector<std::string>& texture_code_names,
|
||||
u32 texture_unit)
|
||||
{
|
||||
for (std::size_t i = 0; i < texture_code_names.size(); i++)
|
||||
{
|
||||
const auto& code_name = texture_code_names[i];
|
||||
out->Write("#define {}_UNIT_{{0}} {}\n", code_name, texture_unit);
|
||||
out->Write(
|
||||
"#define {0}_COORD_{{0}} float3(data.texcoord[data.texmap_to_texcoord_index[{1}]].xy, "
|
||||
"{2})\n",
|
||||
code_name, texture_unit, i + 1);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<CustomPipelineAction>
|
||||
CustomPipelineAction::Create(const picojson::value& json_data,
|
||||
std::shared_ptr<VideoCommon::CustomAssetLibrary> library)
|
||||
{
|
||||
std::vector<CustomPipelineAction::PipelinePassPassDescription> pipeline_passes;
|
||||
|
||||
const auto& passes_json = json_data.get("passes");
|
||||
if (passes_json.is<picojson::array>())
|
||||
{
|
||||
for (const auto& passes_json_val : passes_json.get<picojson::array>())
|
||||
{
|
||||
CustomPipelineAction::PipelinePassPassDescription pipeline_pass;
|
||||
if (!passes_json_val.is<picojson::object>())
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO,
|
||||
"Failed to load custom pipeline action, 'passes' has an array value that "
|
||||
"is not an object!");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto pass = passes_json_val.get<picojson::object>();
|
||||
if (!pass.contains("pixel_material_asset"))
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO,
|
||||
"Failed to load custom pipeline action, 'passes' value missing required "
|
||||
"field 'pixel_material_asset'");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto pixel_material_asset_json = pass["pixel_material_asset"];
|
||||
if (!pixel_material_asset_json.is<std::string>())
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Failed to load custom pipeline action, 'passes' field "
|
||||
"'pixel_material_asset' is not a string!");
|
||||
return nullptr;
|
||||
}
|
||||
pipeline_pass.m_pixel_material_asset = pixel_material_asset_json.to_str();
|
||||
pipeline_passes.push_back(std::move(pipeline_pass));
|
||||
}
|
||||
}
|
||||
|
||||
if (pipeline_passes.empty())
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO, "Failed to load custom pipeline action, must specify at least one pass");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (pipeline_passes.size() > 1)
|
||||
{
|
||||
ERROR_LOG_FMT(
|
||||
VIDEO,
|
||||
"Failed to load custom pipeline action, multiple passes are not currently supported");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return std::make_unique<CustomPipelineAction>(std::move(library), std::move(pipeline_passes));
|
||||
}
|
||||
|
||||
CustomPipelineAction::CustomPipelineAction(
|
||||
std::shared_ptr<VideoCommon::CustomAssetLibrary> library,
|
||||
std::vector<PipelinePassPassDescription> pass_descriptions)
|
||||
: m_library(std::move(library)), m_passes_config(std::move(pass_descriptions))
|
||||
{
|
||||
m_passes.resize(m_passes_config.size());
|
||||
}
|
||||
|
||||
CustomPipelineAction::~CustomPipelineAction() = default;
|
||||
|
||||
void CustomPipelineAction::OnDrawStarted(GraphicsModActionData::DrawStarted* draw_started)
|
||||
{
|
||||
if (!draw_started) [[unlikely]]
|
||||
return;
|
||||
|
||||
if (!draw_started->custom_pixel_shader) [[unlikely]]
|
||||
return;
|
||||
|
||||
if (!m_valid)
|
||||
return;
|
||||
|
||||
if (m_passes.empty()) [[unlikely]]
|
||||
return;
|
||||
|
||||
// For now assume a single pass
|
||||
auto& pass = m_passes[0];
|
||||
|
||||
if (!pass.m_pixel_shader.m_asset) [[unlikely]]
|
||||
return;
|
||||
|
||||
const auto shader_data = pass.m_pixel_shader.m_asset->GetData();
|
||||
if (shader_data)
|
||||
{
|
||||
if (pass.m_pixel_shader.m_asset->GetLastLoadedTime() > pass.m_pixel_shader.m_cached_write_time)
|
||||
{
|
||||
const auto material = pass.m_pixel_material.m_asset->GetData();
|
||||
if (!material)
|
||||
return;
|
||||
|
||||
pass.m_pixel_shader.m_cached_write_time = pass.m_pixel_shader.m_asset->GetLastLoadedTime();
|
||||
|
||||
for (const auto& prop : material->properties)
|
||||
{
|
||||
if (!shader_data->m_properties.contains(prop.m_code_name))
|
||||
{
|
||||
ERROR_LOG_FMT(VIDEO,
|
||||
"Custom pipeline has material asset '{}' that has property '{}'"
|
||||
"that is not on shader asset '{}'",
|
||||
pass.m_pixel_material.m_asset->GetAssetId(), prop.m_code_name,
|
||||
pass.m_pixel_shader.m_asset->GetAssetId());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate shader details
|
||||
std::string color_shader_data =
|
||||
ReplaceAll(shader_data->m_shader_source, "custom_main", CUSTOM_PIXELSHADER_COLOR_FUNC);
|
||||
const auto global_conflicts = GlobalConflicts(color_shader_data);
|
||||
color_shader_data = ReplaceAll(color_shader_data, "\r\n", "\n");
|
||||
color_shader_data = ReplaceAll(color_shader_data, "{", "{{");
|
||||
color_shader_data = ReplaceAll(color_shader_data, "}", "}}");
|
||||
// First replace global conflicts with dummy strings
|
||||
// This avoids the problem where a shorter word
|
||||
// is in a longer word, ex two functions: 'execute' and 'execute_fast'
|
||||
for (std::size_t i = 0; i < global_conflicts.size(); i++)
|
||||
{
|
||||
const std::string& identifier = global_conflicts[i];
|
||||
color_shader_data =
|
||||
ReplaceAll(color_shader_data, identifier, fmt::format("_{0}_DOLPHIN_TEMP_{0}_", i));
|
||||
}
|
||||
// Now replace the temporaries with the actual value
|
||||
for (std::size_t i = 0; i < global_conflicts.size(); i++)
|
||||
{
|
||||
const std::string& identifier = global_conflicts[i];
|
||||
color_shader_data = ReplaceAll(color_shader_data, fmt::format("_{0}_DOLPHIN_TEMP_{0}_", i),
|
||||
fmt::format("{}_{{0}}", identifier));
|
||||
}
|
||||
|
||||
for (const auto& texture_code_name : m_texture_code_names)
|
||||
{
|
||||
color_shader_data =
|
||||
ReplaceAll(color_shader_data, fmt::format("{}_COORD", texture_code_name),
|
||||
fmt::format("{}_COORD_{{0}}", texture_code_name));
|
||||
color_shader_data = ReplaceAll(color_shader_data, fmt::format("{}_UNIT", texture_code_name),
|
||||
fmt::format("{}_UNIT_{{0}}", texture_code_name));
|
||||
}
|
||||
|
||||
m_last_generated_shader_code = ShaderCode{};
|
||||
WriteDefines(&m_last_generated_shader_code, m_texture_code_names, draw_started->texture_unit);
|
||||
m_last_generated_shader_code.Write("{}", color_shader_data);
|
||||
}
|
||||
CustomPixelShader custom_pixel_shader;
|
||||
custom_pixel_shader.custom_shader = m_last_generated_shader_code.GetBuffer();
|
||||
*draw_started->custom_pixel_shader = custom_pixel_shader;
|
||||
}
|
||||
}
|
||||
|
||||
void CustomPipelineAction::OnTextureCreate(GraphicsModActionData::TextureCreate* create)
|
||||
{
|
||||
if (!create->custom_textures) [[unlikely]]
|
||||
return;
|
||||
|
||||
if (!create->additional_dependencies) [[unlikely]]
|
||||
return;
|
||||
|
||||
if (m_passes_config.empty()) [[unlikely]]
|
||||
return;
|
||||
|
||||
if (m_passes.empty()) [[unlikely]]
|
||||
return;
|
||||
|
||||
m_valid = true;
|
||||
auto& loader = Core::System::GetInstance().GetCustomAssetLoader();
|
||||
|
||||
// For now assume a single pass
|
||||
const auto& pass_config = m_passes_config[0];
|
||||
auto& pass = m_passes[0];
|
||||
|
||||
if (!pass.m_pixel_material.m_asset)
|
||||
{
|
||||
pass.m_pixel_material.m_asset =
|
||||
loader.LoadMaterial(pass_config.m_pixel_material_asset, m_library);
|
||||
pass.m_pixel_material.m_cached_write_time = pass.m_pixel_material.m_asset->GetLastLoadedTime();
|
||||
}
|
||||
create->additional_dependencies->push_back(VideoCommon::CachedAsset<VideoCommon::CustomAsset>{
|
||||
pass.m_pixel_material.m_asset, pass.m_pixel_material.m_asset->GetLastLoadedTime()});
|
||||
|
||||
const auto material_data = pass.m_pixel_material.m_asset->GetData();
|
||||
if (!material_data)
|
||||
return;
|
||||
|
||||
if (!pass.m_pixel_shader.m_asset || pass.m_pixel_material.m_asset->GetLastLoadedTime() >
|
||||
pass.m_pixel_material.m_cached_write_time)
|
||||
{
|
||||
pass.m_pixel_shader.m_asset = loader.LoadPixelShader(material_data->shader_asset, m_library);
|
||||
// Note: the asset timestamp will be updated in the draw command
|
||||
}
|
||||
create->additional_dependencies->push_back(VideoCommon::CachedAsset<VideoCommon::CustomAsset>{
|
||||
pass.m_pixel_shader.m_asset, pass.m_pixel_shader.m_asset->GetLastLoadedTime()});
|
||||
|
||||
m_texture_code_names.clear();
|
||||
std::vector<VideoCommon::CachedAsset<VideoCommon::GameTextureAsset>> game_assets;
|
||||
for (const auto& property : material_data->properties)
|
||||
{
|
||||
if (property.m_type == VideoCommon::MaterialProperty::Type::Type_TextureAsset)
|
||||
{
|
||||
if (property.m_value)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Note: we swap here instead of doing a clear + append of the member
|
||||
// variable so that any loaded assets from previous iterations
|
||||
// won't be let go
|
||||
std::swap(pass.m_game_textures, game_assets);
|
||||
|
||||
for (auto& game_texture : pass.m_game_textures)
|
||||
{
|
||||
if (game_texture.m_asset)
|
||||
{
|
||||
auto data = game_texture.m_asset->GetData();
|
||||
if (data)
|
||||
{
|
||||
if (create->texture_width != data->m_levels[0].width ||
|
||||
create->texture_height != data->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_levels[0].width,
|
||||
data->m_levels[0].height);
|
||||
m_valid = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_valid = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: compare game textures and shader requirements
|
||||
|
||||
create->custom_textures->insert(create->custom_textures->end(), pass.m_game_textures.begin(),
|
||||
pass.m_game_textures.end());
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
// Copyright 2022 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <picojson.h>
|
||||
|
||||
#include "VideoCommon/AbstractTexture.h"
|
||||
#include "VideoCommon/Assets/CustomAssetLibrary.h"
|
||||
#include "VideoCommon/Assets/MaterialAsset.h"
|
||||
#include "VideoCommon/Assets/ShaderAsset.h"
|
||||
#include "VideoCommon/Assets/TextureAsset.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h"
|
||||
#include "VideoCommon/ShaderGenCommon.h"
|
||||
|
||||
class CustomPipelineAction final : public GraphicsModAction
|
||||
{
|
||||
public:
|
||||
struct PipelinePassPassDescription
|
||||
{
|
||||
std::string m_pixel_material_asset;
|
||||
};
|
||||
|
||||
static std::unique_ptr<CustomPipelineAction>
|
||||
Create(const picojson::value& json_data,
|
||||
std::shared_ptr<VideoCommon::CustomAssetLibrary> library);
|
||||
CustomPipelineAction(std::shared_ptr<VideoCommon::CustomAssetLibrary> library,
|
||||
std::vector<PipelinePassPassDescription> pass_descriptions);
|
||||
~CustomPipelineAction();
|
||||
void OnDrawStarted(GraphicsModActionData::DrawStarted*) override;
|
||||
void OnTextureCreate(GraphicsModActionData::TextureCreate*) override;
|
||||
|
||||
private:
|
||||
std::shared_ptr<VideoCommon::CustomAssetLibrary> m_library;
|
||||
std::vector<PipelinePassPassDescription> m_passes_config;
|
||||
struct PipelinePass
|
||||
{
|
||||
VideoCommon::CachedAsset<VideoCommon::MaterialAsset> m_pixel_material;
|
||||
VideoCommon::CachedAsset<VideoCommon::PixelShaderAsset> m_pixel_shader;
|
||||
std::vector<VideoCommon::CachedAsset<VideoCommon::GameTextureAsset>> m_game_textures;
|
||||
};
|
||||
std::vector<PipelinePass> m_passes;
|
||||
|
||||
ShaderCode m_last_generated_shader_code;
|
||||
|
||||
bool m_valid = true;
|
||||
|
||||
std::vector<std::string> m_texture_code_names;
|
||||
};
|
|
@ -0,0 +1,376 @@
|
|||
// Copyright 2022 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/CustomShaderCache.h"
|
||||
#include "VideoCommon/AbstractGfx.h"
|
||||
#include "VideoCommon/VideoConfig.h"
|
||||
|
||||
CustomShaderCache::CustomShaderCache()
|
||||
{
|
||||
m_api_type = g_ActiveConfig.backend_info.api_type;
|
||||
m_host_config.bits = ShaderHostConfig::GetCurrent().bits;
|
||||
|
||||
m_async_shader_compiler = g_gfx->CreateAsyncShaderCompiler();
|
||||
m_async_shader_compiler->StartWorkerThreads(1); // TODO
|
||||
|
||||
m_async_uber_shader_compiler = g_gfx->CreateAsyncShaderCompiler();
|
||||
m_async_uber_shader_compiler->StartWorkerThreads(1); // TODO
|
||||
|
||||
m_frame_end_handler =
|
||||
AfterFrameEvent::Register([this] { RetrieveAsyncShaders(); }, "RetreiveAsyncShaders");
|
||||
}
|
||||
|
||||
CustomShaderCache::~CustomShaderCache()
|
||||
{
|
||||
if (m_async_shader_compiler)
|
||||
m_async_shader_compiler->StopWorkerThreads();
|
||||
|
||||
if (m_async_uber_shader_compiler)
|
||||
m_async_uber_shader_compiler->StopWorkerThreads();
|
||||
}
|
||||
|
||||
void CustomShaderCache::RetrieveAsyncShaders()
|
||||
{
|
||||
m_async_shader_compiler->RetrieveWorkItems();
|
||||
m_async_uber_shader_compiler->RetrieveWorkItems();
|
||||
}
|
||||
|
||||
void CustomShaderCache::Reload()
|
||||
{
|
||||
while (m_async_shader_compiler->HasPendingWork() || m_async_shader_compiler->HasCompletedWork())
|
||||
{
|
||||
m_async_shader_compiler->RetrieveWorkItems();
|
||||
}
|
||||
|
||||
while (m_async_uber_shader_compiler->HasPendingWork() ||
|
||||
m_async_uber_shader_compiler->HasCompletedWork())
|
||||
{
|
||||
m_async_uber_shader_compiler->RetrieveWorkItems();
|
||||
}
|
||||
|
||||
m_ps_cache = {};
|
||||
m_uber_ps_cache = {};
|
||||
m_pipeline_cache = {};
|
||||
m_uber_pipeline_cache = {};
|
||||
}
|
||||
|
||||
std::optional<const AbstractPipeline*>
|
||||
CustomShaderCache::GetPipelineAsync(const VideoCommon::GXPipelineUid& uid,
|
||||
const CustomShaderInstance& custom_shaders,
|
||||
const AbstractPipelineConfig& pipeline_config)
|
||||
{
|
||||
if (auto holder = m_pipeline_cache.GetHolder(uid, custom_shaders))
|
||||
{
|
||||
if (holder->pending)
|
||||
return std::nullopt;
|
||||
return holder->value.get();
|
||||
}
|
||||
AsyncCreatePipeline(uid, custom_shaders, pipeline_config);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<const AbstractPipeline*>
|
||||
CustomShaderCache::GetPipelineAsync(const VideoCommon::GXUberPipelineUid& uid,
|
||||
const CustomShaderInstance& custom_shaders,
|
||||
const AbstractPipelineConfig& pipeline_config)
|
||||
{
|
||||
if (auto holder = m_uber_pipeline_cache.GetHolder(uid, custom_shaders))
|
||||
{
|
||||
if (holder->pending)
|
||||
return std::nullopt;
|
||||
return holder->value.get();
|
||||
}
|
||||
AsyncCreatePipeline(uid, custom_shaders, pipeline_config);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void CustomShaderCache::AsyncCreatePipeline(const VideoCommon::GXPipelineUid& uid,
|
||||
|
||||
const CustomShaderInstance& custom_shaders,
|
||||
const AbstractPipelineConfig& pipeline_config)
|
||||
{
|
||||
class PipelineWorkItem final : public VideoCommon::AsyncShaderCompiler::WorkItem
|
||||
{
|
||||
public:
|
||||
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)
|
||||
{
|
||||
SetStagesReady();
|
||||
}
|
||||
|
||||
void SetStagesReady()
|
||||
{
|
||||
m_stages_ready = true;
|
||||
|
||||
PixelShaderUid ps_uid = m_uid.ps_uid;
|
||||
ClearUnusedPixelShaderUidBits(m_shader_cache->m_api_type, m_shader_cache->m_host_config,
|
||||
&ps_uid);
|
||||
|
||||
if (auto holder = m_shader_cache->m_ps_cache.GetHolder(ps_uid, m_custom_shaders))
|
||||
{
|
||||
// If the pixel shader is no longer pending compilation
|
||||
// and the shader compilation succeeded, set
|
||||
// the pipeline to use the new pixel shader.
|
||||
// Otherwise, use the existing shader.
|
||||
if (!holder->pending && holder->value.get())
|
||||
{
|
||||
m_config.pixel_shader = holder->value.get();
|
||||
}
|
||||
m_stages_ready &= !holder->pending;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_stages_ready &= false;
|
||||
m_shader_cache->QueuePixelShaderCompile(ps_uid, m_custom_shaders);
|
||||
}
|
||||
}
|
||||
|
||||
bool Compile() override
|
||||
{
|
||||
if (m_stages_ready)
|
||||
{
|
||||
m_pipeline = g_gfx->CreatePipeline(m_config);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Retrieve() override
|
||||
{
|
||||
if (m_stages_ready)
|
||||
{
|
||||
m_shader_cache->NotifyPipelineFinished(m_iterator, std::move(m_pipeline));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Re-queue for next frame.
|
||||
auto wi = m_shader_cache->m_async_shader_compiler->CreateWorkItem<PipelineWorkItem>(
|
||||
m_shader_cache, m_uid, m_custom_shaders, m_iterator, m_config);
|
||||
m_shader_cache->m_async_shader_compiler->QueueWorkItem(std::move(wi), 0);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
CustomShaderCache* m_shader_cache;
|
||||
std::unique_ptr<AbstractPipeline> m_pipeline;
|
||||
VideoCommon::GXPipelineUid m_uid;
|
||||
PipelineIterator m_iterator;
|
||||
AbstractPipelineConfig m_config;
|
||||
CustomShaderInstance m_custom_shaders;
|
||||
bool m_stages_ready;
|
||||
};
|
||||
|
||||
auto list_iter = m_pipeline_cache.InsertElement(uid, custom_shaders);
|
||||
auto work_item = m_async_shader_compiler->CreateWorkItem<PipelineWorkItem>(
|
||||
this, uid, custom_shaders, list_iter, pipeline_config);
|
||||
m_async_shader_compiler->QueueWorkItem(std::move(work_item), 0);
|
||||
}
|
||||
|
||||
void CustomShaderCache::AsyncCreatePipeline(const VideoCommon::GXUberPipelineUid& uid,
|
||||
|
||||
const CustomShaderInstance& custom_shaders,
|
||||
const AbstractPipelineConfig& pipeline_config)
|
||||
{
|
||||
class PipelineWorkItem final : public VideoCommon::AsyncShaderCompiler::WorkItem
|
||||
{
|
||||
public:
|
||||
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)
|
||||
{
|
||||
SetStagesReady();
|
||||
}
|
||||
|
||||
void SetStagesReady()
|
||||
{
|
||||
m_stages_ready = true;
|
||||
|
||||
UberShader::PixelShaderUid ps_uid = m_uid.ps_uid;
|
||||
ClearUnusedPixelShaderUidBits(m_shader_cache->m_api_type, m_shader_cache->m_host_config,
|
||||
&ps_uid);
|
||||
|
||||
if (auto holder = m_shader_cache->m_uber_ps_cache.GetHolder(ps_uid, m_custom_shaders))
|
||||
{
|
||||
if (!holder->pending && holder->value.get())
|
||||
{
|
||||
m_config.pixel_shader = holder->value.get();
|
||||
}
|
||||
m_stages_ready &= !holder->pending;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_stages_ready &= false;
|
||||
m_shader_cache->QueuePixelShaderCompile(ps_uid, m_custom_shaders);
|
||||
}
|
||||
}
|
||||
|
||||
bool Compile() override
|
||||
{
|
||||
if (m_stages_ready)
|
||||
{
|
||||
if (m_config.pixel_shader == nullptr || m_config.vertex_shader == nullptr)
|
||||
return false;
|
||||
|
||||
m_pipeline = g_gfx->CreatePipeline(m_config);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Retrieve() override
|
||||
{
|
||||
if (m_stages_ready)
|
||||
{
|
||||
m_shader_cache->NotifyPipelineFinished(m_iterator, std::move(m_pipeline));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Re-queue for next frame.
|
||||
auto wi = m_shader_cache->m_async_uber_shader_compiler->CreateWorkItem<PipelineWorkItem>(
|
||||
m_shader_cache, m_uid, m_custom_shaders, m_iterator, m_config);
|
||||
m_shader_cache->m_async_uber_shader_compiler->QueueWorkItem(std::move(wi), 0);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
CustomShaderCache* m_shader_cache;
|
||||
std::unique_ptr<AbstractPipeline> m_pipeline;
|
||||
VideoCommon::GXUberPipelineUid m_uid;
|
||||
UberPipelineIterator m_iterator;
|
||||
AbstractPipelineConfig m_config;
|
||||
CustomShaderInstance m_custom_shaders;
|
||||
bool m_stages_ready;
|
||||
};
|
||||
|
||||
auto list_iter = m_uber_pipeline_cache.InsertElement(uid, custom_shaders);
|
||||
auto work_item = m_async_uber_shader_compiler->CreateWorkItem<PipelineWorkItem>(
|
||||
this, uid, custom_shaders, list_iter, pipeline_config);
|
||||
m_async_uber_shader_compiler->QueueWorkItem(std::move(work_item), 0);
|
||||
}
|
||||
|
||||
void CustomShaderCache::NotifyPipelineFinished(PipelineIterator iterator,
|
||||
std::unique_ptr<AbstractPipeline> pipeline)
|
||||
{
|
||||
iterator->second.pending = false;
|
||||
iterator->second.value = std::move(pipeline);
|
||||
}
|
||||
|
||||
void CustomShaderCache::NotifyPipelineFinished(UberPipelineIterator iterator,
|
||||
std::unique_ptr<AbstractPipeline> pipeline)
|
||||
{
|
||||
iterator->second.pending = false;
|
||||
iterator->second.value = std::move(pipeline);
|
||||
}
|
||||
|
||||
void CustomShaderCache::QueuePixelShaderCompile(const PixelShaderUid& uid,
|
||||
|
||||
const CustomShaderInstance& custom_shaders)
|
||||
{
|
||||
class PixelShaderWorkItem final : public VideoCommon::AsyncShaderCompiler::WorkItem
|
||||
{
|
||||
public:
|
||||
PixelShaderWorkItem(CustomShaderCache* shader_cache, const PixelShaderUid& uid,
|
||||
const CustomShaderInstance& custom_shaders, PixelShaderIterator iter)
|
||||
: m_shader_cache(shader_cache), m_uid(uid), m_custom_shaders(custom_shaders), m_iter(iter)
|
||||
{
|
||||
}
|
||||
|
||||
bool Compile() override
|
||||
{
|
||||
m_shader = m_shader_cache->CompilePixelShader(m_uid, m_custom_shaders);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Retrieve() override
|
||||
{
|
||||
m_shader_cache->NotifyPixelShaderFinished(m_iter, std::move(m_shader));
|
||||
}
|
||||
|
||||
private:
|
||||
CustomShaderCache* m_shader_cache;
|
||||
std::unique_ptr<AbstractShader> m_shader;
|
||||
PixelShaderUid m_uid;
|
||||
CustomShaderInstance m_custom_shaders;
|
||||
PixelShaderIterator m_iter;
|
||||
};
|
||||
|
||||
auto list_iter = m_ps_cache.InsertElement(uid, custom_shaders);
|
||||
auto work_item = m_async_shader_compiler->CreateWorkItem<PixelShaderWorkItem>(
|
||||
this, uid, custom_shaders, list_iter);
|
||||
m_async_shader_compiler->QueueWorkItem(std::move(work_item), 0);
|
||||
}
|
||||
|
||||
void CustomShaderCache::QueuePixelShaderCompile(const UberShader::PixelShaderUid& uid,
|
||||
|
||||
const CustomShaderInstance& custom_shaders)
|
||||
{
|
||||
class PixelShaderWorkItem final : public VideoCommon::AsyncShaderCompiler::WorkItem
|
||||
{
|
||||
public:
|
||||
PixelShaderWorkItem(CustomShaderCache* shader_cache, const UberShader::PixelShaderUid& uid,
|
||||
const CustomShaderInstance& custom_shaders, UberPixelShaderIterator iter)
|
||||
: m_shader_cache(shader_cache), m_uid(uid), m_custom_shaders(custom_shaders), m_iter(iter)
|
||||
{
|
||||
}
|
||||
|
||||
bool Compile() override
|
||||
{
|
||||
m_shader = m_shader_cache->CompilePixelShader(m_uid, m_custom_shaders);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Retrieve() override
|
||||
{
|
||||
m_shader_cache->NotifyPixelShaderFinished(m_iter, std::move(m_shader));
|
||||
}
|
||||
|
||||
private:
|
||||
CustomShaderCache* m_shader_cache;
|
||||
std::unique_ptr<AbstractShader> m_shader;
|
||||
UberShader::PixelShaderUid m_uid;
|
||||
CustomShaderInstance m_custom_shaders;
|
||||
UberPixelShaderIterator m_iter;
|
||||
};
|
||||
|
||||
auto list_iter = m_uber_ps_cache.InsertElement(uid, custom_shaders);
|
||||
auto work_item = m_async_uber_shader_compiler->CreateWorkItem<PixelShaderWorkItem>(
|
||||
this, uid, custom_shaders, list_iter);
|
||||
m_async_uber_shader_compiler->QueueWorkItem(std::move(work_item), 0);
|
||||
}
|
||||
|
||||
std::unique_ptr<AbstractShader>
|
||||
CustomShaderCache::CompilePixelShader(const PixelShaderUid& uid,
|
||||
const CustomShaderInstance& custom_shaders) const
|
||||
{
|
||||
const ShaderCode source_code = GeneratePixelShaderCode(
|
||||
m_api_type, m_host_config, uid.GetUidData(), custom_shaders.pixel_contents);
|
||||
return g_gfx->CreateShaderFromSource(ShaderStage::Pixel, source_code.GetBuffer(),
|
||||
"Custom Pixel Shader");
|
||||
}
|
||||
|
||||
std::unique_ptr<AbstractShader>
|
||||
CustomShaderCache::CompilePixelShader(const UberShader::PixelShaderUid& uid,
|
||||
const CustomShaderInstance& custom_shaders) const
|
||||
{
|
||||
const ShaderCode source_code =
|
||||
GenPixelShader(m_api_type, m_host_config, uid.GetUidData(), custom_shaders.pixel_contents);
|
||||
return g_gfx->CreateShaderFromSource(ShaderStage::Pixel, source_code.GetBuffer(),
|
||||
"Custom Uber Pixel Shader");
|
||||
}
|
||||
|
||||
void CustomShaderCache::NotifyPixelShaderFinished(PixelShaderIterator iterator,
|
||||
std::unique_ptr<AbstractShader> shader)
|
||||
{
|
||||
iterator->second.pending = false;
|
||||
iterator->second.value = std::move(shader);
|
||||
}
|
||||
|
||||
void CustomShaderCache::NotifyPixelShaderFinished(UberPixelShaderIterator iterator,
|
||||
std::unique_ptr<AbstractShader> shader)
|
||||
{
|
||||
iterator->second.pending = false;
|
||||
iterator->second.value = std::move(shader);
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
// Copyright 2022 Dolphin Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include "VideoCommon/AbstractPipeline.h"
|
||||
#include "VideoCommon/AbstractShader.h"
|
||||
#include "VideoCommon/AsyncShaderCompiler.h"
|
||||
#include "VideoCommon/GXPipelineTypes.h"
|
||||
#include "VideoCommon/PixelShaderGen.h"
|
||||
#include "VideoCommon/ShaderGenCommon.h"
|
||||
#include "VideoCommon/UberShaderPixel.h"
|
||||
#include "VideoCommon/VideoEvents.h"
|
||||
|
||||
struct CustomShaderInstance
|
||||
{
|
||||
CustomPixelShaderContents pixel_contents;
|
||||
|
||||
bool operator==(const CustomShaderInstance& other) const = default;
|
||||
};
|
||||
|
||||
class CustomShaderCache
|
||||
{
|
||||
public:
|
||||
CustomShaderCache();
|
||||
~CustomShaderCache();
|
||||
CustomShaderCache(const CustomShaderCache&) = delete;
|
||||
CustomShaderCache(CustomShaderCache&&) = delete;
|
||||
CustomShaderCache& operator=(const CustomShaderCache&) = delete;
|
||||
CustomShaderCache& operator=(CustomShaderCache&&) = delete;
|
||||
|
||||
// Changes the shader host config. Shaders should be reloaded afterwards.
|
||||
void SetHostConfig(const ShaderHostConfig& host_config) { m_host_config.bits = host_config.bits; }
|
||||
|
||||
// Retrieves all pending shaders/pipelines from the async compiler.
|
||||
void RetrieveAsyncShaders();
|
||||
|
||||
// Reloads/recreates all shaders and pipelines.
|
||||
void Reload();
|
||||
|
||||
// The optional will be empty if this pipeline is now background compiling.
|
||||
std::optional<const AbstractPipeline*>
|
||||
GetPipelineAsync(const VideoCommon::GXPipelineUid& uid,
|
||||
const CustomShaderInstance& custom_shaders,
|
||||
const AbstractPipelineConfig& pipeline_config);
|
||||
std::optional<const AbstractPipeline*>
|
||||
GetPipelineAsync(const VideoCommon::GXUberPipelineUid& uid,
|
||||
const CustomShaderInstance& custom_shaders,
|
||||
const AbstractPipelineConfig& pipeline_config);
|
||||
|
||||
private:
|
||||
// Configuration bits.
|
||||
APIType m_api_type = APIType::Nothing;
|
||||
ShaderHostConfig m_host_config = {};
|
||||
std::unique_ptr<VideoCommon::AsyncShaderCompiler> m_async_shader_compiler;
|
||||
std::unique_ptr<VideoCommon::AsyncShaderCompiler> m_async_uber_shader_compiler;
|
||||
|
||||
void AsyncCreatePipeline(const VideoCommon::GXPipelineUid& uid,
|
||||
const CustomShaderInstance& custom_shaders,
|
||||
const AbstractPipelineConfig& pipeline_config);
|
||||
void AsyncCreatePipeline(const VideoCommon::GXUberPipelineUid& uid,
|
||||
const CustomShaderInstance& custom_shaders,
|
||||
const AbstractPipelineConfig& pipeline_config);
|
||||
|
||||
// Shader/Pipeline cache helper
|
||||
template <typename Uid, typename ValueType>
|
||||
struct Cache
|
||||
{
|
||||
struct CacheHolder
|
||||
{
|
||||
std::unique_ptr<ValueType> value = nullptr;
|
||||
bool pending = true;
|
||||
};
|
||||
using CacheElement = std::pair<CustomShaderInstance, CacheHolder>;
|
||||
using CacheList = std::list<CacheElement>;
|
||||
std::map<Uid, CacheList> uid_to_cachelist;
|
||||
|
||||
const CacheHolder* GetHolder(const Uid& uid, const CustomShaderInstance& custom_shaders) const
|
||||
{
|
||||
if (auto uuid_it = uid_to_cachelist.find(uid); uuid_it != uid_to_cachelist.end())
|
||||
{
|
||||
for (const auto& [custom_shader_val, holder] : uuid_it->second)
|
||||
{
|
||||
if (custom_shaders == custom_shader_val)
|
||||
{
|
||||
return &holder;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
typename CacheList::iterator InsertElement(const Uid& uid,
|
||||
const CustomShaderInstance& custom_shaders)
|
||||
{
|
||||
CacheList& cachelist = uid_to_cachelist[uid];
|
||||
CacheElement e{custom_shaders, CacheHolder{}};
|
||||
return cachelist.emplace(cachelist.begin(), std::move(e));
|
||||
}
|
||||
};
|
||||
|
||||
Cache<PixelShaderUid, AbstractShader> m_ps_cache;
|
||||
Cache<UberShader::PixelShaderUid, AbstractShader> m_uber_ps_cache;
|
||||
Cache<VideoCommon::GXPipelineUid, AbstractPipeline> m_pipeline_cache;
|
||||
Cache<VideoCommon::GXUberPipelineUid, AbstractPipeline> m_uber_pipeline_cache;
|
||||
|
||||
using PipelineIterator = Cache<VideoCommon::GXPipelineUid, AbstractPipeline>::CacheList::iterator;
|
||||
using UberPipelineIterator =
|
||||
Cache<VideoCommon::GXUberPipelineUid, AbstractPipeline>::CacheList::iterator;
|
||||
using PixelShaderIterator = Cache<PixelShaderUid, AbstractShader>::CacheList::iterator;
|
||||
using UberPixelShaderIterator =
|
||||
Cache<UberShader::PixelShaderUid, AbstractShader>::CacheList::iterator;
|
||||
|
||||
void NotifyPipelineFinished(PipelineIterator iterator,
|
||||
std::unique_ptr<AbstractPipeline> pipeline);
|
||||
void NotifyPipelineFinished(UberPipelineIterator iterator,
|
||||
std::unique_ptr<AbstractPipeline> pipeline);
|
||||
|
||||
std::unique_ptr<AbstractShader>
|
||||
CompilePixelShader(const PixelShaderUid& uid, const CustomShaderInstance& custom_shaders) const;
|
||||
void NotifyPixelShaderFinished(PixelShaderIterator iterator,
|
||||
std::unique_ptr<AbstractShader> shader);
|
||||
std::unique_ptr<AbstractShader>
|
||||
CompilePixelShader(const UberShader::PixelShaderUid& uid,
|
||||
const CustomShaderInstance& custom_shaders) const;
|
||||
void NotifyPixelShaderFinished(UberPixelShaderIterator iterator,
|
||||
std::unique_ptr<AbstractShader> shader);
|
||||
|
||||
void QueuePixelShaderCompile(const PixelShaderUid& uid,
|
||||
const CustomShaderInstance& custom_shaders);
|
||||
void QueuePixelShaderCompile(const UberShader::PixelShaderUid& uid,
|
||||
const CustomShaderInstance& custom_shaders);
|
||||
|
||||
Common::EventHook m_frame_end_handler;
|
||||
};
|
|
@ -3,18 +3,23 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
#include "Common/Matrix.h"
|
||||
#include "VideoCommon/Assets/TextureAsset.h"
|
||||
#include "VideoCommon/PixelShaderGen.h"
|
||||
|
||||
namespace GraphicsModActionData
|
||||
{
|
||||
struct DrawStarted
|
||||
{
|
||||
u32 texture_unit;
|
||||
bool* skip;
|
||||
std::optional<CustomPixelShader>* custom_pixel_shader;
|
||||
};
|
||||
|
||||
struct EFB
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.h"
|
||||
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/Actions/CustomPipelineAction.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/Actions/MoveAction.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/Actions/PrintAction.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/Actions/ScaleAction.h"
|
||||
|
@ -11,7 +12,7 @@
|
|||
namespace GraphicsModActionFactory
|
||||
{
|
||||
std::unique_ptr<GraphicsModAction> Create(std::string_view name, const picojson::value& json_data,
|
||||
std::string_view path)
|
||||
std::shared_ptr<VideoCommon::CustomAssetLibrary> library)
|
||||
{
|
||||
if (name == "print")
|
||||
{
|
||||
|
@ -29,6 +30,10 @@ std::unique_ptr<GraphicsModAction> Create(std::string_view name, const picojson:
|
|||
{
|
||||
return ScaleAction::Create(json_data);
|
||||
}
|
||||
else if (name == "custom_pipeline")
|
||||
{
|
||||
return CustomPipelineAction::Create(json_data, std::move(library));
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
|
|
@ -8,10 +8,11 @@
|
|||
|
||||
#include <picojson.h>
|
||||
|
||||
#include "VideoCommon/Assets/CustomAssetLibrary.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h"
|
||||
|
||||
namespace GraphicsModActionFactory
|
||||
{
|
||||
std::unique_ptr<GraphicsModAction> Create(std::string_view name, const picojson::value& json_data,
|
||||
std::string_view path);
|
||||
std::shared_ptr<VideoCommon::CustomAssetLibrary> library);
|
||||
}
|
||||
|
|
|
@ -13,7 +13,9 @@
|
|||
|
||||
#include "Core/ConfigManager.h"
|
||||
|
||||
#include "VideoCommon/Assets/DirectFilesystemAssetLibrary.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Config/GraphicsMod.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Config/GraphicsModAsset.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Config/GraphicsModGroup.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionFactory.h"
|
||||
#include "VideoCommon/TextureInfo.h"
|
||||
|
@ -187,6 +189,8 @@ void GraphicsModManager::Load(const GraphicsModGroupConfig& config)
|
|||
|
||||
const auto& mods = config.GetMods();
|
||||
|
||||
auto filesystem_library = std::make_shared<VideoCommon::DirectFilesystemAssetLibrary>();
|
||||
|
||||
std::map<std::string, std::vector<GraphicsTargetConfig>> group_to_targets;
|
||||
for (const auto& mod : mods)
|
||||
{
|
||||
|
@ -208,6 +212,29 @@ void GraphicsModManager::Load(const GraphicsModGroupConfig& config)
|
|||
group_to_targets[internal_group].push_back(target);
|
||||
}
|
||||
}
|
||||
|
||||
std::string base_path;
|
||||
SplitPath(mod.GetAbsolutePath(), &base_path, nullptr, nullptr);
|
||||
for (const GraphicsModAssetConfig& asset : mod.m_assets)
|
||||
{
|
||||
auto asset_map = asset.m_map;
|
||||
for (auto& [k, v] : asset_map)
|
||||
{
|
||||
if (v.is_absolute())
|
||||
{
|
||||
WARN_LOG_FMT(VIDEO,
|
||||
"Specified graphics mod asset '{}' for mod '{}' has an absolute path, you "
|
||||
"shouldn't release this to users.",
|
||||
asset.m_name, mod.m_title);
|
||||
}
|
||||
else
|
||||
{
|
||||
v = std::filesystem::path{base_path} / v;
|
||||
}
|
||||
}
|
||||
|
||||
filesystem_library->SetAssetIDMapData(asset.m_name, std::move(asset_map));
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& mod : mods)
|
||||
|
@ -215,12 +242,11 @@ void GraphicsModManager::Load(const GraphicsModGroupConfig& config)
|
|||
for (const GraphicsModFeatureConfig& feature : mod.m_features)
|
||||
{
|
||||
const auto create_action =
|
||||
[](const std::string_view& action_name, const picojson::value& json_data,
|
||||
GraphicsModConfig mod_config) -> std::unique_ptr<GraphicsModAction> {
|
||||
std::string base_path;
|
||||
SplitPath(mod_config.GetAbsolutePath(), &base_path, nullptr, nullptr);
|
||||
|
||||
auto action = GraphicsModActionFactory::Create(action_name, json_data, base_path);
|
||||
[filesystem_library](const std::string_view& action_name,
|
||||
const picojson::value& json_data,
|
||||
GraphicsModConfig mod_config) -> std::unique_ptr<GraphicsModAction> {
|
||||
auto action =
|
||||
GraphicsModActionFactory::Create(action_name, json_data, std::move(filesystem_library));
|
||||
if (action == nullptr)
|
||||
{
|
||||
return nullptr;
|
||||
|
|
|
@ -175,3 +175,180 @@ void GetLightingShaderUid(LightingUidData& uid_data)
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GenerateCustomLightingHeaderDetails(ShaderCode* out, u32 enablelighting, u32 light_mask)
|
||||
{
|
||||
u32 light_count = 0;
|
||||
for (u32 j = 0; j < NUM_XF_COLOR_CHANNELS; j++)
|
||||
{
|
||||
if ((enablelighting & (1 << j)) != 0) // Color lights
|
||||
{
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
if ((light_mask & (1 << (i + 8 * j))) != 0)
|
||||
{
|
||||
light_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((enablelighting & (1 << (j + 2))) != 0) // Alpha lights
|
||||
{
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
if ((light_mask & (1 << (i + 8 * (j + 2)))) != 0)
|
||||
{
|
||||
light_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (light_count > 0)
|
||||
{
|
||||
out->Write("\tCustomShaderLightData[{}] light;\n", light_count);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Cheat so shaders compile
|
||||
out->Write("\tCustomShaderLightData[1] light;\n", light_count);
|
||||
}
|
||||
out->Write("\tint light_count;\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++)
|
||||
{
|
||||
out->Write("\tcustom_data.lights_chan{}_color[{}].direction = float3(0, 0, 0);\n",
|
||||
channel_index, i);
|
||||
out->Write("\tcustom_data.lights_chan{}_color[{}].position = float3(0, 0, 0);\n",
|
||||
channel_index, i);
|
||||
out->Write("\tcustom_data.lights_chan{}_color[{}].color = float3(0, 0, 0);\n", channel_index,
|
||||
i);
|
||||
out->Write("\tcustom_data.lights_chan{}_color[{}].cosatt = float4(0, 0, 0, 0);\n",
|
||||
channel_index, i);
|
||||
out->Write("\tcustom_data.lights_chan{}_color[{}].distatt = float4(0, 0, 0, 0);\n",
|
||||
channel_index, i);
|
||||
out->Write("\tcustom_data.lights_chan{}_color[{}].attenuation_type = 0;\n", channel_index, i);
|
||||
|
||||
out->Write("\tcustom_data.lights_chan{}_alpha[{}].direction = float3(0, 0, 0);\n",
|
||||
channel_index, i);
|
||||
out->Write("\tcustom_data.lights_chan{}_alpha[{}].position = float3(0, 0, 0);\n",
|
||||
channel_index, i);
|
||||
out->Write("\tcustom_data.lights_chan{}_alpha[{}].color = float3(0, 0, 0);\n", channel_index,
|
||||
i);
|
||||
out->Write("\tcustom_data.lights_chan{}_alpha[{}].cosatt = float4(0, 0, 0, 0);\n",
|
||||
channel_index, i);
|
||||
out->Write("\tcustom_data.lights_chan{}_alpha[{}].distatt = float4(0, 0, 0, 0);\n",
|
||||
channel_index, i);
|
||||
out->Write("\tcustom_data.lights_chan{}_alpha[{}].attenuation_type = 0;\n", channel_index, i);
|
||||
}
|
||||
}
|
||||
|
||||
for (u32 j = 0; j < NUM_XF_COLOR_CHANNELS; j++)
|
||||
{
|
||||
const bool colormatsource = !!(uid_data.matsource & (1 << j));
|
||||
if (colormatsource) // from vertex
|
||||
out->Write("custom_data.base_material[{}] = {}{};\n", j, in_color_name, j);
|
||||
else // from color
|
||||
out->Write("custom_data.base_material[{}] = {}[{}] / 255.0;\n", j, I_MATERIALS, j + 2);
|
||||
|
||||
if ((uid_data.enablelighting & (1 << j)) != 0)
|
||||
{
|
||||
if ((uid_data.ambsource & (1 << j)) != 0) // from vertex
|
||||
out->Write("custom_data.ambient_lighting[{}] = {}{};\n", j, in_color_name, j);
|
||||
else // from color
|
||||
out->Write("custom_data.ambient_lighting[{}] = {}[{}] / 255.0;\n", j, I_MATERIALS, j);
|
||||
}
|
||||
else
|
||||
{
|
||||
out->Write("custom_data.ambient_lighting[{}] = float4(1, 1, 1, 1);\n", j);
|
||||
}
|
||||
|
||||
// check if alpha is different
|
||||
const bool alphamatsource = !!(uid_data.matsource & (1 << (j + 2)));
|
||||
if (alphamatsource != colormatsource)
|
||||
{
|
||||
if (alphamatsource) // from vertex
|
||||
out->Write("custom_data.base_material[{}].w = {}{}.w;\n", j, in_color_name, j);
|
||||
else // from color
|
||||
out->Write("custom_data.base_material[{}].w = {}[{}].w / 255.0;\n", j, I_MATERIALS, j + 2);
|
||||
}
|
||||
|
||||
if ((uid_data.enablelighting & (1 << (j + 2))) != 0)
|
||||
{
|
||||
if ((uid_data.ambsource & (1 << (j + 2))) != 0) // from vertex
|
||||
out->Write("custom_data.ambient_lighting[{}].w = {}{}.w;\n", j, in_color_name, j);
|
||||
else // from color
|
||||
out->Write("custom_data.ambient_lighting[{}].w = {}[{}].w / 255.0;\n", j, I_MATERIALS, j);
|
||||
}
|
||||
else
|
||||
{
|
||||
out->Write("custom_data.ambient_lighting[{}].w = 1;\n", j);
|
||||
}
|
||||
|
||||
u32 light_count = 0;
|
||||
if ((uid_data.enablelighting & (1 << j)) != 0) // Color lights
|
||||
{
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
if ((uid_data.light_mask & (1 << (i + 8 * j))) != 0)
|
||||
{
|
||||
generate_lighting(out, uid_data, i, j, j, light_count, false);
|
||||
light_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
out->Write("\tcustom_data.light_chan{}_color_count = {};\n", j, light_count);
|
||||
|
||||
light_count = 0;
|
||||
if ((uid_data.enablelighting & (1 << (j + 2))) != 0) // Alpha lights
|
||||
{
|
||||
for (int i = 0; i < 8; ++i)
|
||||
{
|
||||
if ((uid_data.light_mask & (1 << (i + 8 * (j + 2)))) != 0)
|
||||
{
|
||||
generate_lighting(out, uid_data, i, j + 2, j, light_count, true);
|
||||
light_count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
out->Write("\tcustom_data.light_chan{}_alpha_count = {};\n", j, light_count);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,3 +47,7 @@ constexpr char s_lighting_struct[] = "struct Light {\n"
|
|||
void GenerateLightingShaderCode(ShaderCode& object, const LightingUidData& uid_data,
|
||||
std::string_view in_color_name, std::string_view dest);
|
||||
void GetLightingShaderUid(LightingUidData& uid_data);
|
||||
|
||||
void GenerateCustomLightingHeaderDetails(ShaderCode* out, u32 enablelighting, u32 light_mask);
|
||||
void GenerateCustomLightingImplementation(ShaderCode* out, const LightingUidData& uid_data,
|
||||
std::string_view in_color_name);
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "VideoCommon/PixelShaderGen.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <fmt/format.h>
|
||||
|
@ -130,6 +131,17 @@ constexpr Common::EnumMap<const char*, TevColorArg::Zero> tev_c_input_table{
|
|||
"int3(0,0,0)", // ZERO
|
||||
};
|
||||
|
||||
constexpr Common::EnumMap<const char*, TevColorArg::Zero> tev_c_input_type{
|
||||
"CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_PREV", "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_PREV",
|
||||
"CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR", "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR",
|
||||
"CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR", "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR",
|
||||
"CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR", "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR",
|
||||
"CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_TEX", "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_TEX",
|
||||
"CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_RAS", "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_RAS",
|
||||
"CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_NUMERIC", "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_NUMERIC",
|
||||
"CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_KONST", "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_NUMERIC",
|
||||
};
|
||||
|
||||
constexpr Common::EnumMap<const char*, TevAlphaArg::Zero> tev_a_input_table{
|
||||
"prev.a", // APREV,
|
||||
"c0.a", // A0,
|
||||
|
@ -141,6 +153,13 @@ constexpr Common::EnumMap<const char*, TevAlphaArg::Zero> tev_a_input_table{
|
|||
"0", // ZERO
|
||||
};
|
||||
|
||||
constexpr Common::EnumMap<const char*, TevAlphaArg::Zero> tev_a_input_type{
|
||||
"CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_PREV", "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR",
|
||||
"CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR", "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR",
|
||||
"CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_TEX", "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_RAS",
|
||||
"CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_KONST", "CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_NUMERIC",
|
||||
};
|
||||
|
||||
constexpr Common::EnumMap<const char*, RasColorChan::Zero> tev_ras_table{
|
||||
"iround(col0 * 255.0)",
|
||||
"iround(col1 * 255.0)",
|
||||
|
@ -387,6 +406,7 @@ void WritePixelShaderCommonHeader(ShaderCode& out, APIType api_type,
|
|||
"\tbool blend_subtract_alpha;\n"
|
||||
"\tbool logic_op_enable;\n"
|
||||
"\tuint logic_op_mode;\n"
|
||||
"\tuint time_ms;\n"
|
||||
"}};\n\n");
|
||||
out.Write("#define bpmem_combiners(i) (bpmem_pack1[(i)].xy)\n"
|
||||
"#define bpmem_tevind(i) (bpmem_pack1[(i)].z)\n"
|
||||
|
@ -732,8 +752,131 @@ uint WrapCoord(int coord, uint wrap, int size) {{
|
|||
}
|
||||
}
|
||||
|
||||
void WriteCustomShaderStructImpl(ShaderCode* out, u32 num_stages, bool per_pixel_lighting,
|
||||
const pixel_shader_uid_data* uid_data)
|
||||
{
|
||||
out->Write("\tCustomShaderData custom_data;\n");
|
||||
|
||||
if (per_pixel_lighting)
|
||||
{
|
||||
out->Write("\tcustom_data.position = WorldPos;\n");
|
||||
out->Write("\tcustom_data.normal = Normal;\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
out->Write("\tcustom_data.position = float3(0, 0, 0);\n");
|
||||
out->Write("\tcustom_data.normal = float3(0, 0, 0);\n");
|
||||
}
|
||||
|
||||
if (uid_data->genMode_numtexgens == 0) [[unlikely]]
|
||||
{
|
||||
out->Write("\tcustom_data.texcoord[0] = float3(0, 0, 0);\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
for (u32 i = 0; i < uid_data->genMode_numtexgens; ++i)
|
||||
{
|
||||
out->Write("\tif (tex{0}.z == 0.0)\n", i);
|
||||
out->Write("\t{{\n");
|
||||
out->Write("\t\tcustom_data.texcoord[{0}] = tex{0};\n", i);
|
||||
out->Write("\t}}\n");
|
||||
out->Write("\telse {{\n");
|
||||
out->Write("\t\tcustom_data.texcoord[{0}] = float3(tex{0}.xy / tex{0}.z, 0);\n", i);
|
||||
out->Write("\t}}\n");
|
||||
}
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < 8; i++)
|
||||
{
|
||||
// Shader compilation complains if every index isn't initialized
|
||||
out->Write("\tcustom_data.texmap_to_texcoord_index[{0}] = 0;\n", i);
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < uid_data->genMode_numindstages; ++i)
|
||||
{
|
||||
if ((uid_data->nIndirectStagesUsed & (1U << i)) != 0)
|
||||
{
|
||||
u32 texcoord = uid_data->GetTevindirefCoord(i);
|
||||
const u32 texmap = uid_data->GetTevindirefMap(i);
|
||||
|
||||
// Quirk: when the tex coord is not less than the number of tex gens (i.e. the tex coord does
|
||||
// not exist), then tex coord 0 is used (though sometimes glitchy effects happen on console).
|
||||
// This affects the Mario portrait in Luigi's Mansion, where the developers forgot to set
|
||||
// the number of tex gens to 2 (bug 11462).
|
||||
if (texcoord >= uid_data->genMode_numtexgens)
|
||||
texcoord = 0;
|
||||
|
||||
out->Write("\tcustom_data.texmap_to_texcoord_index[{}] = {};\n", texmap, texcoord);
|
||||
}
|
||||
}
|
||||
out->Write("\tcustom_data.texcoord_count = {};\n", uid_data->genMode_numtexgens);
|
||||
|
||||
// Try and do a best guess on what the texcoord index is
|
||||
// Note: one issue with this would be textures that are used
|
||||
// multiple times in the same draw but with different texture coordinates.
|
||||
// In that scenario, only the last texture coordinate would be defined.
|
||||
// This issue can be seen in how Rogue Squadron 2 does bump mapping
|
||||
for (u32 i = 0; i < num_stages; i++)
|
||||
{
|
||||
auto& tevstage = uid_data->stagehash[i];
|
||||
// Quirk: when the tex coord is not less than the number of tex gens (i.e. the tex coord does
|
||||
// not exist), then tex coord 0 is used (though sometimes glitchy effects happen on console).
|
||||
u32 texcoord = tevstage.tevorders_texcoord;
|
||||
const bool has_tex_coord = texcoord < uid_data->genMode_numtexgens;
|
||||
if (!has_tex_coord)
|
||||
texcoord = 0;
|
||||
|
||||
out->Write("\tcustom_data.texmap_to_texcoord_index[{}] = {};\n", tevstage.tevorders_texmap,
|
||||
texcoord);
|
||||
}
|
||||
|
||||
GenerateCustomLightingImplementation(out, uid_data->lighting, "colors_");
|
||||
|
||||
for (u32 i = 0; i < 16; i++)
|
||||
{
|
||||
// Shader compilation complains if every struct isn't initialized
|
||||
|
||||
// Color Input
|
||||
for (u32 j = 0; j < 4; j++)
|
||||
{
|
||||
out->Write("\tcustom_data.tev_stages[{}].input_color[{}].input_type = "
|
||||
"CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_UNUSED;\n",
|
||||
i, j);
|
||||
out->Write("\tcustom_data.tev_stages[{}].input_color[{}].value = "
|
||||
"float3(0, 0, 0);\n",
|
||||
i, j);
|
||||
}
|
||||
|
||||
// Alpha Input
|
||||
for (u32 j = 0; j < 4; j++)
|
||||
{
|
||||
out->Write("\tcustom_data.tev_stages[{}].input_alpha[{}].input_type = "
|
||||
"CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_UNUSED;\n",
|
||||
i, j);
|
||||
out->Write("\tcustom_data.tev_stages[{}].input_alpha[{}].value = "
|
||||
"float(0);\n",
|
||||
i, j);
|
||||
}
|
||||
|
||||
// Texmap
|
||||
out->Write("\tcustom_data.tev_stages[{}].texmap = 0u;\n", i);
|
||||
|
||||
// Output
|
||||
out->Write("\tcustom_data.tev_stages[{}].output_color = "
|
||||
"float4(0, 0, 0, 0);\n",
|
||||
i);
|
||||
}
|
||||
|
||||
// Actual data will be filled out in the tev stage code, just set the
|
||||
// stage count for now
|
||||
out->Write("\tcustom_data.tev_stage_count = {};\n", num_stages);
|
||||
|
||||
// Time
|
||||
out->Write("\tcustom_data.time_ms = time_ms;\n");
|
||||
}
|
||||
|
||||
static void WriteStage(ShaderCode& out, const pixel_shader_uid_data* uid_data, int n,
|
||||
APIType api_type, bool stereo);
|
||||
APIType api_type, bool stereo, bool has_custom_shaders);
|
||||
static void WriteTevRegular(ShaderCode& out, std::string_view components, TevBias bias, TevOp op,
|
||||
bool clamp, TevScale scale);
|
||||
static void WriteAlphaTest(ShaderCode& out, const pixel_shader_uid_data* uid_data, APIType api_type,
|
||||
|
@ -746,7 +889,8 @@ static void WriteColor(ShaderCode& out, APIType api_type, const pixel_shader_uid
|
|||
static void WriteBlend(ShaderCode& out, const pixel_shader_uid_data* uid_data);
|
||||
|
||||
ShaderCode GeneratePixelShaderCode(APIType api_type, const ShaderHostConfig& host_config,
|
||||
const pixel_shader_uid_data* uid_data)
|
||||
const pixel_shader_uid_data* uid_data,
|
||||
const CustomPixelShaderContents& custom_details)
|
||||
{
|
||||
ShaderCode out;
|
||||
|
||||
|
@ -762,8 +906,17 @@ 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);
|
||||
|
||||
// Custom shader details
|
||||
WriteCustomShaderStructDef(&out, uid_data->genMode_numtexgens);
|
||||
for (std::size_t i = 0; i < custom_details.shaders.size(); i++)
|
||||
{
|
||||
const auto& shader_details = custom_details.shaders[i];
|
||||
out.Write(fmt::runtime(shader_details.custom_shader), i);
|
||||
}
|
||||
|
||||
out.Write("\n#define sampleTextureWrapper(texmap, uv, layer) "
|
||||
"sampleTexture(texmap, samp[texmap], uv, layer)\n");
|
||||
|
||||
|
@ -892,6 +1045,14 @@ ShaderCode GeneratePixelShaderCode(APIType api_type, const ShaderHostConfig& hos
|
|||
out.Write("void main()\n{{\n");
|
||||
out.Write("\tfloat4 rawpos = gl_FragCoord;\n");
|
||||
|
||||
bool has_custom_shaders = false;
|
||||
if (std::any_of(custom_details.shaders.begin(), custom_details.shaders.end(),
|
||||
[](const std::optional<CustomPixelShader>& ps) { return ps.has_value(); }))
|
||||
{
|
||||
WriteCustomShaderStructImpl(&out, numStages, per_pixel_lighting, uid_data);
|
||||
has_custom_shaders = true;
|
||||
}
|
||||
|
||||
if (use_framebuffer_fetch)
|
||||
{
|
||||
// Store off a copy of the initial framebuffer value.
|
||||
|
@ -1013,7 +1174,7 @@ ShaderCode GeneratePixelShaderCode(APIType api_type, const ShaderHostConfig& hos
|
|||
for (u32 i = 0; i < numStages; i++)
|
||||
{
|
||||
// Build the equation for this stage
|
||||
WriteStage(out, uid_data, i, api_type, stereo);
|
||||
WriteStage(out, uid_data, i, api_type, stereo, has_custom_shaders);
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -1146,7 +1307,21 @@ ShaderCode GeneratePixelShaderCode(APIType api_type, const ShaderHostConfig& hos
|
|||
|
||||
// Write the color and alpha values to the framebuffer
|
||||
// If using shader blend, we still use the separate alpha
|
||||
WriteColor(out, api_type, uid_data, !uid_data->no_dual_src || uid_data->blend_enable);
|
||||
const bool use_dual_source = !uid_data->no_dual_src || uid_data->blend_enable;
|
||||
WriteColor(out, api_type, uid_data, use_dual_source);
|
||||
|
||||
for (std::size_t i = 0; i < custom_details.shaders.size(); i++)
|
||||
{
|
||||
const auto& shader_details = custom_details.shaders[i];
|
||||
|
||||
if (!shader_details.custom_shader.empty())
|
||||
{
|
||||
out.Write("\t{{\n");
|
||||
out.Write("\t\tcustom_data.final_color = ocol0;\n");
|
||||
out.Write("\t\tocol0.xyz = {}_{}(custom_data).xyz;\n", CUSTOM_PIXELSHADER_COLOR_FUNC, i);
|
||||
out.Write("\t}}\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (uid_data->blend_enable)
|
||||
WriteBlend(out, uid_data);
|
||||
|
@ -1162,7 +1337,7 @@ ShaderCode GeneratePixelShaderCode(APIType api_type, const ShaderHostConfig& hos
|
|||
}
|
||||
|
||||
static void WriteStage(ShaderCode& out, const pixel_shader_uid_data* uid_data, int n,
|
||||
APIType api_type, bool stereo)
|
||||
APIType api_type, bool stereo, bool has_custom_shaders)
|
||||
{
|
||||
using Common::EnumMap;
|
||||
|
||||
|
@ -1556,6 +1731,58 @@ static void WriteStage(ShaderCode& out, const pixel_shader_uid_data* uid_data, i
|
|||
out.Write(", -1024, 1023)");
|
||||
|
||||
out.Write(";\n");
|
||||
|
||||
if (has_custom_shaders)
|
||||
{
|
||||
// Color input
|
||||
out.Write(
|
||||
"\tcustom_data.tev_stages[{}].input_color[0].value = {} / float3(255.0, 255.0, 255.0);\n",
|
||||
n, tev_c_input_table[cc.a]);
|
||||
out.Write("\tcustom_data.tev_stages[{}].input_color[0].input_type = {};\n", n,
|
||||
tev_c_input_type[cc.a]);
|
||||
out.Write(
|
||||
"\tcustom_data.tev_stages[{}].input_color[1].value = {} / float3(255.0, 255.0, 255.0);\n",
|
||||
n, tev_c_input_table[cc.b]);
|
||||
out.Write("\tcustom_data.tev_stages[{}].input_color[1].input_type = {};\n", n,
|
||||
tev_c_input_type[cc.b]);
|
||||
out.Write(
|
||||
"\tcustom_data.tev_stages[{}].input_color[2].value = {} / float3(255.0, 255.0, 255.0);\n",
|
||||
n, tev_c_input_table[cc.c]);
|
||||
out.Write("\tcustom_data.tev_stages[{}].input_color[2].input_type = {};\n", n,
|
||||
tev_c_input_type[cc.c]);
|
||||
out.Write(
|
||||
"\tcustom_data.tev_stages[{}].input_color[3].value = {} / float3(255.0, 255.0, 255.0);\n",
|
||||
n, tev_c_input_table[cc.d]);
|
||||
out.Write("\tcustom_data.tev_stages[{}].input_color[3].input_type = {};\n", n,
|
||||
tev_c_input_type[cc.d]);
|
||||
|
||||
// Alpha input
|
||||
out.Write("\tcustom_data.tev_stages[{}].input_alpha[0].value = {} / float(255.0);\n", n,
|
||||
tev_a_input_table[ac.a]);
|
||||
out.Write("\tcustom_data.tev_stages[{}].input_alpha[0].input_type = {};\n", n,
|
||||
tev_a_input_type[ac.a]);
|
||||
out.Write("\tcustom_data.tev_stages[{}].input_alpha[1].value = {} / float(255.0);\n", n,
|
||||
tev_a_input_table[ac.b]);
|
||||
out.Write("\tcustom_data.tev_stages[{}].input_alpha[1].input_type = {};\n", n,
|
||||
tev_a_input_type[ac.b]);
|
||||
out.Write("\tcustom_data.tev_stages[{}].input_alpha[2].value = {} / float(255.0);\n", n,
|
||||
tev_a_input_table[ac.c]);
|
||||
out.Write("\tcustom_data.tev_stages[{}].input_alpha[2].input_type = {};\n", n,
|
||||
tev_a_input_type[ac.c]);
|
||||
out.Write("\tcustom_data.tev_stages[{}].input_alpha[3].value = {} / float(255.0);\n", n,
|
||||
tev_a_input_table[ac.d]);
|
||||
out.Write("\tcustom_data.tev_stages[{}].input_alpha[3].input_type = {};\n", n,
|
||||
tev_a_input_type[ac.d]);
|
||||
|
||||
// Texmap
|
||||
out.Write("\tcustom_data.tev_stages[{}].texmap = {}u;\n", n, stage.tevorders_texmap);
|
||||
|
||||
// Output
|
||||
out.Write("\tcustom_data.tev_stages[{}].output_color.rgb = {} / float3(255.0, 255.0, 255.0);\n",
|
||||
n, tev_c_output_table[cc.dest]);
|
||||
out.Write("\tcustom_data.tev_stages[{}].output_color.a = {} / float(255.0);\n", n,
|
||||
tev_a_output_table[ac.dest]);
|
||||
}
|
||||
}
|
||||
|
||||
static void WriteTevRegular(ShaderCode& out, std::string_view components, TevBias bias, TevOp op,
|
||||
|
|
|
@ -158,8 +158,12 @@ struct pixel_shader_uid_data
|
|||
|
||||
using PixelShaderUid = ShaderUid<pixel_shader_uid_data>;
|
||||
|
||||
void WriteCustomShaderStructImpl(ShaderCode* out, u32 num_stages, bool per_pixel_lighting,
|
||||
const pixel_shader_uid_data* uid_data);
|
||||
|
||||
ShaderCode GeneratePixelShaderCode(APIType api_type, const ShaderHostConfig& host_config,
|
||||
const pixel_shader_uid_data* uid_data);
|
||||
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);
|
||||
void ClearUnusedPixelShaderUidBits(APIType api_type, const ShaderHostConfig& host_config,
|
||||
|
|
|
@ -419,9 +419,9 @@ std::vector<std::string> PostProcessing::GetPassiveShaderList()
|
|||
bool PostProcessing::Initialize(AbstractTextureFormat format)
|
||||
{
|
||||
m_framebuffer_format = format;
|
||||
// CompilePixelShader must be run first if configuration options are used.
|
||||
// CompilePixelShader() must be run first if configuration options are used.
|
||||
// Otherwise the UBO has a different member list between vertex and pixel
|
||||
// shaders, which is a link error.
|
||||
// shaders, which is a link error on some backends.
|
||||
if (!CompilePixelShader() || !CompileVertexShader() || !CompilePipeline())
|
||||
return false;
|
||||
|
||||
|
@ -486,23 +486,29 @@ void PostProcessing::BlitFromTexture(const MathUtil::Rectangle<int>& dst,
|
|||
|
||||
MathUtil::Rectangle<int> src_rect = src;
|
||||
g_gfx->SetSamplerState(0, RenderState::GetLinearSamplerState());
|
||||
g_gfx->SetSamplerState(1, RenderState::GetPointSamplerState());
|
||||
g_gfx->SetTexture(0, src_tex);
|
||||
g_gfx->SetTexture(1, src_tex);
|
||||
|
||||
const bool is_color_correction_active = IsColorCorrectionActive();
|
||||
const bool needs_color_correction = IsColorCorrectionActive();
|
||||
// Rely on the default (bi)linear sampler with the default mode
|
||||
// (it might not be gamma corrected).
|
||||
const bool needs_resampling =
|
||||
g_ActiveConfig.output_resampling_mode > OutputResamplingMode::Default;
|
||||
const bool needs_intermediary_buffer = NeedsIntermediaryBuffer();
|
||||
const bool needs_default_pipeline = needs_color_correction || needs_resampling;
|
||||
const AbstractPipeline* final_pipeline = m_pipeline.get();
|
||||
std::vector<u8>* uniform_staging_buffer = &m_default_uniform_staging_buffer;
|
||||
bool default_uniform_staging_buffer = true;
|
||||
const MathUtil::Rectangle<int> present_rect = g_presenter->GetTargetRectangle();
|
||||
|
||||
// Intermediary pass.
|
||||
// We draw to a high quality intermediary texture for two reasons:
|
||||
// We draw to a high quality intermediary texture for a couple reasons:
|
||||
// -Consistently do high quality gamma corrected resampling (upscaling/downscaling)
|
||||
// -Keep quality for gamma and gamut conversions, and HDR output
|
||||
// (low bit depths lose too much quality with gamma conversions)
|
||||
// -We make a texture of the exact same res as the source one,
|
||||
// because all the post process shaders we already had assume that
|
||||
// the source texture size (EFB) is different from the swap chain
|
||||
// texture size (which matches the window size).
|
||||
if (m_default_pipeline && is_color_correction_active && needs_intermediary_buffer)
|
||||
// -Keep the post process phase in linear space, to better operate with colors
|
||||
if (m_default_pipeline && needs_default_pipeline && needs_intermediary_buffer)
|
||||
{
|
||||
AbstractFramebuffer* const previous_framebuffer = g_gfx->GetCurrentFramebuffer();
|
||||
|
||||
|
@ -512,13 +518,18 @@ void PostProcessing::BlitFromTexture(const MathUtil::Rectangle<int>& dst,
|
|||
// so it would be a waste to allocate two layers (see "bUsesExplictQuadBuffering").
|
||||
const u32 target_layers = copy_all_layers ? src_tex->GetLayers() : 1;
|
||||
|
||||
const u32 target_width =
|
||||
needs_resampling ? present_rect.GetWidth() : static_cast<u32>(src_rect.GetWidth());
|
||||
const u32 target_height =
|
||||
needs_resampling ? present_rect.GetHeight() : static_cast<u32>(src_rect.GetHeight());
|
||||
|
||||
if (!m_intermediary_frame_buffer || !m_intermediary_color_texture ||
|
||||
m_intermediary_color_texture.get()->GetWidth() != static_cast<u32>(src_rect.GetWidth()) ||
|
||||
m_intermediary_color_texture.get()->GetHeight() != static_cast<u32>(src_rect.GetHeight()) ||
|
||||
m_intermediary_color_texture.get()->GetWidth() != target_width ||
|
||||
m_intermediary_color_texture.get()->GetHeight() != target_height ||
|
||||
m_intermediary_color_texture.get()->GetLayers() != target_layers)
|
||||
{
|
||||
const TextureConfig intermediary_color_texture_config(
|
||||
src_rect.GetWidth(), src_rect.GetHeight(), 1, target_layers, src_tex->GetSamples(),
|
||||
target_width, target_height, 1, target_layers, src_tex->GetSamples(),
|
||||
s_intermediary_buffer_format, AbstractTextureFlag_RenderTarget);
|
||||
m_intermediary_color_texture = g_gfx->CreateTexture(intermediary_color_texture_config,
|
||||
"Intermediary post process texture");
|
||||
|
@ -530,8 +541,8 @@ void PostProcessing::BlitFromTexture(const MathUtil::Rectangle<int>& dst,
|
|||
g_gfx->SetFramebuffer(m_intermediary_frame_buffer.get());
|
||||
|
||||
FillUniformBuffer(src_rect, src_tex, src_layer, g_gfx->GetCurrentFramebuffer()->GetRect(),
|
||||
g_presenter->GetTargetRectangle(), uniform_staging_buffer->data(),
|
||||
!default_uniform_staging_buffer);
|
||||
present_rect, uniform_staging_buffer->data(), !default_uniform_staging_buffer,
|
||||
true);
|
||||
g_vertex_manager->UploadUtilityUniforms(uniform_staging_buffer->data(),
|
||||
static_cast<u32>(uniform_staging_buffer->size()));
|
||||
|
||||
|
@ -544,6 +555,7 @@ void PostProcessing::BlitFromTexture(const MathUtil::Rectangle<int>& dst,
|
|||
src_rect = m_intermediary_color_texture->GetRect();
|
||||
src_tex = m_intermediary_color_texture.get();
|
||||
g_gfx->SetTexture(0, src_tex);
|
||||
g_gfx->SetTexture(1, src_tex);
|
||||
// The "m_intermediary_color_texture" has already copied
|
||||
// from the specified source layer onto its first one.
|
||||
// If we query for a layer that the source texture doesn't have,
|
||||
|
@ -557,7 +569,7 @@ void PostProcessing::BlitFromTexture(const MathUtil::Rectangle<int>& dst,
|
|||
// If we have no custom user shader selected, and color correction
|
||||
// is active, directly run the fixed pipeline shader instead of
|
||||
// doing two passes, with the second one doing nothing useful.
|
||||
if (m_default_pipeline && is_color_correction_active)
|
||||
if (m_default_pipeline && needs_default_pipeline)
|
||||
{
|
||||
final_pipeline = m_default_pipeline.get();
|
||||
}
|
||||
|
@ -580,8 +592,8 @@ void PostProcessing::BlitFromTexture(const MathUtil::Rectangle<int>& dst,
|
|||
if (final_pipeline)
|
||||
{
|
||||
FillUniformBuffer(src_rect, src_tex, src_layer, g_gfx->GetCurrentFramebuffer()->GetRect(),
|
||||
g_presenter->GetTargetRectangle(), uniform_staging_buffer->data(),
|
||||
!default_uniform_staging_buffer);
|
||||
present_rect, uniform_staging_buffer->data(), !default_uniform_staging_buffer,
|
||||
false);
|
||||
g_vertex_manager->UploadUtilityUniforms(uniform_staging_buffer->data(),
|
||||
static_cast<u32>(uniform_staging_buffer->size()));
|
||||
|
||||
|
@ -609,7 +621,11 @@ std::string PostProcessing::GetUniformBufferHeader(bool user_post_process) const
|
|||
// The first (but not necessarily only) source layer we target
|
||||
ss << " int src_layer;\n";
|
||||
ss << " uint time;\n";
|
||||
ss << " int graphics_api;\n";
|
||||
// If true, it's an intermediary buffer (including the first), if false, it's the final one
|
||||
ss << " int intermediary_buffer;\n";
|
||||
|
||||
ss << " int resampling_method;\n";
|
||||
ss << " int correct_color_space;\n";
|
||||
ss << " int game_color_space;\n";
|
||||
ss << " int correct_gamma;\n";
|
||||
|
@ -742,6 +758,7 @@ void SetOutput(float4 color)
|
|||
|
||||
#define GetOption(x) (x)
|
||||
#define OptionEnabled(x) ((x) != 0)
|
||||
#define OptionDisabled(x) ((x) == 0)
|
||||
|
||||
)";
|
||||
return ss.str();
|
||||
|
@ -752,13 +769,9 @@ std::string PostProcessing::GetFooter() const
|
|||
return {};
|
||||
}
|
||||
|
||||
bool PostProcessing::CompileVertexShader()
|
||||
std::string GetVertexShaderBody()
|
||||
{
|
||||
std::ostringstream ss;
|
||||
// We never need the user selected post process custom uniforms in the vertex shader
|
||||
const bool user_post_process = false;
|
||||
ss << GetUniformBufferHeader(user_post_process);
|
||||
|
||||
if (g_ActiveConfig.backend_info.bSupportsGeometryShaders)
|
||||
{
|
||||
ss << "VARYING_LOCATION(0) out VertexData {\n";
|
||||
|
@ -779,21 +792,34 @@ bool PostProcessing::CompileVertexShader()
|
|||
|
||||
// Vulkan Y needs to be inverted on every pass
|
||||
if (g_ActiveConfig.backend_info.api_type == APIType::Vulkan)
|
||||
{
|
||||
ss << " opos.y = -opos.y;\n";
|
||||
|
||||
std::string s2 = ss.str();
|
||||
s2 += "}\n";
|
||||
m_default_vertex_shader = g_gfx->CreateShaderFromSource(ShaderStage::Vertex, s2,
|
||||
"Default post-processing vertex shader");
|
||||
|
||||
// OpenGL Y needs to be inverted once only (in the last pass)
|
||||
if (g_ActiveConfig.backend_info.api_type == APIType::OpenGL)
|
||||
ss << " opos.y = -opos.y;\n";
|
||||
}
|
||||
// OpenGL Y needs to be inverted in all passes except the last one
|
||||
else if (g_ActiveConfig.backend_info.api_type == APIType::OpenGL)
|
||||
{
|
||||
ss << " if (intermediary_buffer != 0)\n";
|
||||
ss << " opos.y = -opos.y;\n";
|
||||
}
|
||||
|
||||
ss << "}\n";
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
bool PostProcessing::CompileVertexShader()
|
||||
{
|
||||
std::ostringstream ss_default;
|
||||
ss_default << GetUniformBufferHeader(false);
|
||||
ss_default << GetVertexShaderBody();
|
||||
m_default_vertex_shader = g_gfx->CreateShaderFromSource(ShaderStage::Vertex, ss_default.str(),
|
||||
"Default post-processing vertex shader");
|
||||
|
||||
std::ostringstream ss;
|
||||
ss << GetUniformBufferHeader(true);
|
||||
ss << GetVertexShaderBody();
|
||||
m_vertex_shader =
|
||||
g_gfx->CreateShaderFromSource(ShaderStage::Vertex, ss.str(), "Post-processing vertex shader");
|
||||
|
||||
if (!m_default_vertex_shader || !m_vertex_shader)
|
||||
{
|
||||
PanicAlertFmt("Failed to compile post-processing vertex shader");
|
||||
|
@ -816,6 +842,9 @@ struct BuiltinUniforms
|
|||
std::array<float, 4> src_rect;
|
||||
s32 src_layer;
|
||||
u32 time;
|
||||
s32 graphics_api;
|
||||
s32 intermediary_buffer;
|
||||
s32 resampling_method;
|
||||
s32 correct_color_space;
|
||||
s32 game_color_space;
|
||||
s32 correct_gamma;
|
||||
|
@ -839,7 +868,7 @@ void PostProcessing::FillUniformBuffer(const MathUtil::Rectangle<int>& src,
|
|||
const AbstractTexture* src_tex, int src_layer,
|
||||
const MathUtil::Rectangle<int>& dst,
|
||||
const MathUtil::Rectangle<int>& wnd, u8* buffer,
|
||||
bool user_post_process)
|
||||
bool user_post_process, bool intermediary_buffer)
|
||||
{
|
||||
const float rcp_src_width = 1.0f / src_tex->GetWidth();
|
||||
const float rcp_src_height = 1.0f / src_tex->GetHeight();
|
||||
|
@ -860,7 +889,10 @@ void PostProcessing::FillUniformBuffer(const MathUtil::Rectangle<int>& src,
|
|||
static_cast<float>(src.GetHeight()) * rcp_src_height};
|
||||
builtin_uniforms.src_layer = static_cast<s32>(src_layer);
|
||||
builtin_uniforms.time = static_cast<u32>(m_timer.ElapsedMs());
|
||||
builtin_uniforms.graphics_api = static_cast<s32>(g_ActiveConfig.backend_info.api_type);
|
||||
builtin_uniforms.intermediary_buffer = static_cast<s32>(intermediary_buffer);
|
||||
|
||||
builtin_uniforms.resampling_method = static_cast<s32>(g_ActiveConfig.output_resampling_mode);
|
||||
// Color correction related uniforms.
|
||||
// These are mainly used by the "m_default_pixel_shader",
|
||||
// but should also be accessible to all other shaders.
|
||||
|
@ -883,6 +915,8 @@ void PostProcessing::FillUniformBuffer(const MathUtil::Rectangle<int>& src,
|
|||
std::memcpy(buffer, &builtin_uniforms, sizeof(builtin_uniforms));
|
||||
buffer += sizeof(builtin_uniforms);
|
||||
|
||||
// Don't include the custom pp shader options if they are not necessary,
|
||||
// having mismatching uniforms between different shaders can cause issues on some backends
|
||||
if (!user_post_process)
|
||||
return;
|
||||
|
||||
|
@ -1000,8 +1034,7 @@ bool PostProcessing::CompilePipeline()
|
|||
const bool needs_intermediary_buffer = NeedsIntermediaryBuffer();
|
||||
|
||||
AbstractPipelineConfig config = {};
|
||||
config.vertex_shader =
|
||||
needs_intermediary_buffer ? m_vertex_shader.get() : m_default_vertex_shader.get();
|
||||
config.vertex_shader = m_default_vertex_shader.get();
|
||||
// This geometry shader will take care of reading both layer 0 and 1 on the source texture,
|
||||
// and writing to both layer 0 and 1 on the render target.
|
||||
config.geometry_shader = UseGeometryShaderForPostProcess(needs_intermediary_buffer) ?
|
||||
|
@ -1018,7 +1051,7 @@ bool PostProcessing::CompilePipeline()
|
|||
if (config.pixel_shader)
|
||||
m_default_pipeline = g_gfx->CreatePipeline(config);
|
||||
|
||||
config.vertex_shader = m_default_vertex_shader.get();
|
||||
config.vertex_shader = m_vertex_shader.get();
|
||||
config.geometry_shader = UseGeometryShaderForPostProcess(false) ?
|
||||
g_shader_cache->GetTexcoordGeometryShader() :
|
||||
nullptr;
|
||||
|
|
|
@ -124,7 +124,8 @@ protected:
|
|||
size_t CalculateUniformsSize(bool user_post_process) const;
|
||||
void FillUniformBuffer(const MathUtil::Rectangle<int>& src, const AbstractTexture* src_tex,
|
||||
int src_layer, const MathUtil::Rectangle<int>& dst,
|
||||
const MathUtil::Rectangle<int>& wnd, u8* buffer, bool user_post_process);
|
||||
const MathUtil::Rectangle<int>& wnd, u8* buffer, bool user_post_process,
|
||||
bool intermediary_buffer);
|
||||
|
||||
// Timer for determining our time value
|
||||
Common::Timer m_timer;
|
||||
|
|
|
@ -449,7 +449,7 @@ ShaderCache::CompileVertexUberShader(const UberShader::VertexShaderUid& uid) con
|
|||
std::unique_ptr<AbstractShader> ShaderCache::CompilePixelShader(const PixelShaderUid& uid) const
|
||||
{
|
||||
const ShaderCode source_code =
|
||||
GeneratePixelShaderCode(m_api_type, m_host_config, uid.GetUidData());
|
||||
GeneratePixelShaderCode(m_api_type, m_host_config, uid.GetUidData(), {});
|
||||
return g_gfx->CreateShaderFromSource(ShaderStage::Pixel, source_code.GetBuffer());
|
||||
}
|
||||
|
||||
|
@ -457,7 +457,7 @@ std::unique_ptr<AbstractShader>
|
|||
ShaderCache::CompilePixelUberShader(const UberShader::PixelShaderUid& uid) const
|
||||
{
|
||||
const ShaderCode source_code =
|
||||
UberShader::GenPixelShader(m_api_type, m_host_config, uid.GetUidData());
|
||||
UberShader::GenPixelShader(m_api_type, m_host_config, uid.GetUidData(), {});
|
||||
return g_gfx->CreateShaderFromSource(ShaderStage::Pixel, source_code.GetBuffer(),
|
||||
fmt::to_string(*uid.GetUidData()));
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "Core/ConfigManager.h"
|
||||
#include "VideoCommon/VideoCommon.h"
|
||||
#include "VideoCommon/VideoConfig.h"
|
||||
#include "VideoCommon/XFMemory.h"
|
||||
|
||||
ShaderHostConfig ShaderHostConfig::GetCurrent()
|
||||
{
|
||||
|
@ -362,3 +363,90 @@ const char* GetInterpolationQualifier(bool msaa, bool ssaa, bool in_glsl_interfa
|
|||
return "sample";
|
||||
}
|
||||
}
|
||||
|
||||
void WriteCustomShaderStructDef(ShaderCode* out, u32 numtexgens)
|
||||
{
|
||||
// Bump this when there are breaking changes to the API
|
||||
out->Write("#define CUSTOM_SHADER_API_VERSION 1;\n");
|
||||
|
||||
// CUSTOM_SHADER_LIGHTING_ATTENUATION_TYPE "enum" values
|
||||
out->Write("const uint CUSTOM_SHADER_LIGHTING_ATTENUATION_TYPE_NONE = {}u;\n",
|
||||
static_cast<u32>(AttenuationFunc::None));
|
||||
out->Write("const uint CUSTOM_SHADER_LIGHTING_ATTENUATION_TYPE_POINT = {}u;\n",
|
||||
static_cast<u32>(AttenuationFunc::Spec));
|
||||
out->Write("const uint CUSTOM_SHADER_LIGHTING_ATTENUATION_TYPE_DIR = {}u;\n",
|
||||
static_cast<u32>(AttenuationFunc::Dir));
|
||||
out->Write("const uint CUSTOM_SHADER_LIGHTING_ATTENUATION_TYPE_SPOT = {}u;\n",
|
||||
static_cast<u32>(AttenuationFunc::Spot));
|
||||
|
||||
out->Write("struct CustomShaderLightData\n");
|
||||
out->Write("{{\n");
|
||||
out->Write("\tfloat3 position;\n");
|
||||
out->Write("\tfloat3 direction;\n");
|
||||
out->Write("\tfloat3 color;\n");
|
||||
out->Write("\tuint attenuation_type;\n");
|
||||
out->Write("\tfloat4 cosatt;\n");
|
||||
out->Write("\tfloat4 distatt;\n");
|
||||
out->Write("}};\n\n");
|
||||
|
||||
// CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE "enum" values
|
||||
out->Write("const uint CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_PREV = 0u;\n");
|
||||
out->Write("const uint CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR = 1u;\n");
|
||||
out->Write("const uint CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_TEX = 2u;\n");
|
||||
out->Write("const uint CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_RAS = 3u;\n");
|
||||
out->Write("const uint CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_KONST = 4u;\n");
|
||||
out->Write("const uint CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_NUMERIC = 5u;\n");
|
||||
out->Write("const uint CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_UNUSED = 6u;\n");
|
||||
|
||||
out->Write("struct CustomShaderTevStageInputColor\n");
|
||||
out->Write("{{\n");
|
||||
out->Write("\tuint input_type;\n");
|
||||
out->Write("\tfloat3 value;\n");
|
||||
out->Write("}};\n\n");
|
||||
|
||||
out->Write("struct CustomShaderTevStageInputAlpha\n");
|
||||
out->Write("{{\n");
|
||||
out->Write("\tuint input_type;\n");
|
||||
out->Write("\tfloat value;\n");
|
||||
out->Write("}};\n\n");
|
||||
|
||||
out->Write("struct CustomShaderTevStage\n");
|
||||
out->Write("{{\n");
|
||||
out->Write("\tCustomShaderTevStageInputColor[4] input_color;\n");
|
||||
out->Write("\tCustomShaderTevStageInputAlpha[4] input_alpha;\n");
|
||||
out->Write("\tuint texmap;\n");
|
||||
out->Write("\tfloat4 output_color;\n");
|
||||
out->Write("}};\n\n");
|
||||
|
||||
// Custom structure for data we pass to custom shader hooks
|
||||
out->Write("struct CustomShaderData\n");
|
||||
out->Write("{{\n");
|
||||
out->Write("\tfloat3 position;\n");
|
||||
out->Write("\tfloat3 normal;\n");
|
||||
if (numtexgens == 0)
|
||||
{
|
||||
// Cheat so shaders compile
|
||||
out->Write("\tfloat3[1] texcoord;\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
out->Write("\tfloat3[{}] texcoord;\n", numtexgens);
|
||||
}
|
||||
out->Write("\tuint texcoord_count;\n");
|
||||
out->Write("\tuint[8] texmap_to_texcoord_index;\n");
|
||||
out->Write("\tCustomShaderLightData[8] lights_chan0_color;\n");
|
||||
out->Write("\tCustomShaderLightData[8] lights_chan0_alpha;\n");
|
||||
out->Write("\tCustomShaderLightData[8] lights_chan1_color;\n");
|
||||
out->Write("\tCustomShaderLightData[8] lights_chan1_alpha;\n");
|
||||
out->Write("\tfloat4[2] ambient_lighting;\n");
|
||||
out->Write("\tfloat4[2] base_material;\n");
|
||||
out->Write("\tuint light_chan0_color_count;\n");
|
||||
out->Write("\tuint light_chan0_alpha_count;\n");
|
||||
out->Write("\tuint light_chan1_color_count;\n");
|
||||
out->Write("\tuint light_chan1_alpha_count;\n");
|
||||
out->Write("\tCustomShaderTevStage[16] tev_stages;\n");
|
||||
out->Write("\tuint tev_stage_count;\n");
|
||||
out->Write("\tfloat4 final_color;\n");
|
||||
out->Write("\tuint time_ms;\n");
|
||||
out->Write("}};\n\n");
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
|
@ -327,3 +328,21 @@ static const char s_geometry_shader_uniforms[] = "\tfloat4 " I_STEREOPARAMS ";\n
|
|||
"\tfloat4 " I_LINEPTPARAMS ";\n"
|
||||
"\tint4 " I_TEXOFFSET ";\n"
|
||||
"\tuint vs_expand;\n";
|
||||
|
||||
constexpr std::string_view CUSTOM_PIXELSHADER_COLOR_FUNC = "customShaderColor";
|
||||
|
||||
struct CustomPixelShader
|
||||
{
|
||||
std::string custom_shader;
|
||||
|
||||
bool operator==(const CustomPixelShader& other) const = default;
|
||||
};
|
||||
|
||||
struct CustomPixelShaderContents
|
||||
{
|
||||
std::vector<CustomPixelShader> shaders;
|
||||
|
||||
bool operator==(const CustomPixelShaderContents& other) const = default;
|
||||
};
|
||||
|
||||
void WriteCustomShaderStructDef(ShaderCode* out, u32 numtexgens);
|
||||
|
|
|
@ -17,6 +17,260 @@
|
|||
|
||||
namespace UberShader
|
||||
{
|
||||
namespace
|
||||
{
|
||||
void WriteCustomShaderStructImpl(ShaderCode* out, u32 num_texgen, bool per_pixel_lighting)
|
||||
{
|
||||
out->Write("\tCustomShaderData custom_data;\n");
|
||||
if (per_pixel_lighting)
|
||||
{
|
||||
out->Write("\tcustom_data.position = WorldPos;\n");
|
||||
out->Write("\tcustom_data.normal = Normal;\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
out->Write("\tcustom_data.position = float3(0, 0, 0);\n");
|
||||
out->Write("\tcustom_data.normal = float3(0, 0, 0);\n");
|
||||
}
|
||||
|
||||
if (num_texgen == 0) [[unlikely]]
|
||||
{
|
||||
out->Write("\tcustom_data.texcoord[0] = float3(0, 0, 0);\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
for (u32 i = 0; i < num_texgen; ++i)
|
||||
{
|
||||
out->Write("\tif (tex{0}.z == 0.0)\n", i);
|
||||
out->Write("\t{{\n");
|
||||
out->Write("\t\tcustom_data.texcoord[{0}] = tex{0};\n", i);
|
||||
out->Write("\t}}\n");
|
||||
out->Write("\telse {{\n");
|
||||
out->Write("\t\tcustom_data.texcoord[{0}] = float3(tex{0}.xy / tex{0}.z, 0);\n", i);
|
||||
out->Write("\t}}\n");
|
||||
}
|
||||
}
|
||||
|
||||
out->Write("\tcustom_data.texcoord_count = {};\n", num_texgen);
|
||||
|
||||
for (u32 i = 0; i < 8; i++)
|
||||
{
|
||||
// Shader compilation complains if every index isn't initialized
|
||||
out->Write("\tcustom_data.texmap_to_texcoord_index[{0}] = {0};\n", i);
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < NUM_XF_COLOR_CHANNELS; i++)
|
||||
{
|
||||
out->Write("\tcustom_data.base_material[{}] = vec4(0, 0, 0, 1);\n", i);
|
||||
out->Write("\tcustom_data.ambient_lighting[{}] = vec4(0, 0, 0, 1);\n", i);
|
||||
|
||||
// Shader compilation errors can throw if not everything is initialized
|
||||
for (u32 light_count_index = 0; light_count_index < 8; light_count_index++)
|
||||
{
|
||||
// Color
|
||||
out->Write("\tcustom_data.lights_chan{}_color[{}].direction = float3(0, 0, 0);\n", i,
|
||||
light_count_index);
|
||||
out->Write("\tcustom_data.lights_chan{}_color[{}].position = float3(0, 0, 0);\n", i,
|
||||
light_count_index);
|
||||
out->Write("\tcustom_data.lights_chan{}_color[{}].color = float3(0, 0, 0);\n", i,
|
||||
light_count_index);
|
||||
out->Write("\tcustom_data.lights_chan{}_color[{}].cosatt = float4(0, 0, 0, 0);\n", i,
|
||||
light_count_index);
|
||||
out->Write("\tcustom_data.lights_chan{}_color[{}].distatt = float4(0, 0, 0, 0);\n", i,
|
||||
light_count_index);
|
||||
out->Write("\tcustom_data.lights_chan{}_color[{}].attenuation_type = 0;\n", i,
|
||||
light_count_index);
|
||||
|
||||
// Alpha
|
||||
out->Write("\tcustom_data.lights_chan{}_alpha[{}].direction = float3(0, 0, 0);\n", i,
|
||||
light_count_index);
|
||||
out->Write("\tcustom_data.lights_chan{}_alpha[{}].position = float3(0, 0, 0);\n", i,
|
||||
light_count_index);
|
||||
out->Write("\tcustom_data.lights_chan{}_alpha[{}].color = float3(0, 0, 0);\n", i,
|
||||
light_count_index);
|
||||
out->Write("\tcustom_data.lights_chan{}_alpha[{}].cosatt = float4(0, 0, 0, 0);\n", i,
|
||||
light_count_index);
|
||||
out->Write("\tcustom_data.lights_chan{}_alpha[{}].distatt = float4(0, 0, 0, 0);\n", i,
|
||||
light_count_index);
|
||||
out->Write("\tcustom_data.lights_chan{}_alpha[{}].attenuation_type = 0;\n", i,
|
||||
light_count_index);
|
||||
}
|
||||
|
||||
out->Write("\tcustom_data.light_chan{}_color_count = 0;\n", i);
|
||||
out->Write("\tcustom_data.light_chan{}_alpha_count = 0;\n", i);
|
||||
}
|
||||
|
||||
if (num_texgen > 0) [[likely]]
|
||||
{
|
||||
out->Write("\n");
|
||||
out->Write("\tfor(uint stage = 0u; stage <= num_stages; stage++)\n");
|
||||
out->Write("\t{{\n");
|
||||
out->Write("\t\tStageState ss;\n");
|
||||
out->Write("\t\tss.order = bpmem_tevorder(stage>>1);\n");
|
||||
out->Write("\t\tif ((stage & 1u) == 1u)\n");
|
||||
out->Write("\t\t\tss.order = ss.order >> {};\n\n",
|
||||
int(TwoTevStageOrders().enable_tex_odd.StartBit() -
|
||||
TwoTevStageOrders().enable_tex_even.StartBit()));
|
||||
out->Write("\t\tuint texmap = {};\n",
|
||||
BitfieldExtract<&TwoTevStageOrders::texcoord_even>("ss.order"));
|
||||
// Shader compilation is weird, shader arrays can't use indexing by variable
|
||||
// to set values unless the variable is an index in a for loop.
|
||||
// So instead we have to do this if check nonsense
|
||||
for (u32 i = 0; i < 8; i++)
|
||||
{
|
||||
out->Write("\t\tif (texmap == {})\n", i);
|
||||
out->Write("\t\t{{\n");
|
||||
out->Write("\t\t\tcustom_data.texmap_to_texcoord_index[{}] = selectTexCoordIndex(texmap);\n",
|
||||
i);
|
||||
out->Write("\t\t}}\n");
|
||||
}
|
||||
out->Write("\t}}\n");
|
||||
}
|
||||
|
||||
out->Write("\tuint light_count = 0;\n");
|
||||
out->Write("\tfor (uint chan = 0u; chan < {}u; chan++)\n", NUM_XF_COLOR_CHANNELS);
|
||||
out->Write("\t{{\n");
|
||||
out->Write("\t\tuint colorreg = xfmem_color(chan);\n");
|
||||
out->Write("\t\tuint alphareg = xfmem_alpha(chan);\n");
|
||||
for (const auto& color_type : std::array<std::string_view, 2>{"colorreg", "alphareg"})
|
||||
{
|
||||
if (color_type == "colorreg")
|
||||
{
|
||||
out->Write("\t\tcustom_data.base_material[0] = " I_MATERIALS "[2u] / 255.0; \n");
|
||||
out->Write("\t\tif ({} != 0u)\n", BitfieldExtract<&LitChannel::enablelighting>(color_type));
|
||||
out->Write("\t\t\tcustom_data.base_material[0] = colors_0; \n");
|
||||
}
|
||||
else
|
||||
{
|
||||
out->Write("custom_data.base_material[1].w = " I_MATERIALS "[3u].w / 255.0; \n");
|
||||
out->Write("\t\tif ({} != 0u)\n", BitfieldExtract<&LitChannel::enablelighting>(color_type));
|
||||
out->Write("\t\t\tcustom_data.base_material[1].w = colors_1.w; \n");
|
||||
}
|
||||
out->Write("\t\tif ({} != 0u)\n", BitfieldExtract<&LitChannel::enablelighting>(color_type));
|
||||
out->Write("\t\t{{\n");
|
||||
out->Write("\t\t\tuint light_mask = {} | ({} << 4u);\n",
|
||||
BitfieldExtract<&LitChannel::lightMask0_3>(color_type),
|
||||
BitfieldExtract<&LitChannel::lightMask4_7>(color_type));
|
||||
out->Write("\t\t\tuint attnfunc = {};\n", BitfieldExtract<&LitChannel::attnfunc>(color_type));
|
||||
out->Write("\t\t\tfor (uint light_index = 0u; light_index < 8u; light_index++)\n");
|
||||
out->Write("\t\t\t{{\n");
|
||||
out->Write("\t\t\t\tif ((light_mask & (1u << light_index)) != 0u)\n");
|
||||
out->Write("\t\t\t\t{{\n");
|
||||
// Shader compilation is weird, shader arrays can't use indexing by variable
|
||||
// to set values unless the variable is an index in a for loop.
|
||||
// So instead we have to do this if check nonsense
|
||||
for (u32 light_count_index = 0; light_count_index < 8; light_count_index++)
|
||||
{
|
||||
out->Write("\t\t\t\t\tif (light_index == {})\n", light_count_index);
|
||||
out->Write("\t\t\t\t\t{{\n");
|
||||
if (color_type == "colorreg")
|
||||
{
|
||||
for (u32 channel_index = 0; channel_index < NUM_XF_COLOR_CHANNELS; channel_index++)
|
||||
{
|
||||
out->Write("\t\t\t\t\t\tif (chan == {})\n", channel_index);
|
||||
out->Write("\t\t\t\t\t\t{{\n");
|
||||
out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_color[{}].direction = " I_LIGHTS
|
||||
"[light_index].dir.xyz;\n",
|
||||
channel_index, light_count_index);
|
||||
out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_color[{}].position = " I_LIGHTS
|
||||
"[light_index].pos.xyz;\n",
|
||||
channel_index, light_count_index);
|
||||
out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_color[{}].cosatt = " I_LIGHTS
|
||||
"[light_index].cosatt;\n",
|
||||
channel_index, light_count_index);
|
||||
out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_color[{}].distatt = " I_LIGHTS
|
||||
"[light_index].distatt;\n",
|
||||
channel_index, light_count_index);
|
||||
out->Write(
|
||||
"\t\t\t\t\t\t\tcustom_data.lights_chan{}_color[{}].attenuation_type = attnfunc;\n",
|
||||
channel_index, light_count_index);
|
||||
out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_color[{}].color = " I_LIGHTS
|
||||
"[light_index].color.rgb / float3(255.0, 255.0, 255.0);\n",
|
||||
channel_index, light_count_index);
|
||||
out->Write("\t\t\t\t\t\t\tcustom_data.light_chan{}_color_count += 1;\n", channel_index);
|
||||
out->Write("\t\t\t\t\t\t}}\n");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (u32 channel_index = 0; channel_index < NUM_XF_COLOR_CHANNELS; channel_index++)
|
||||
{
|
||||
out->Write("\t\t\t\t\t\tif (chan == {})\n", channel_index);
|
||||
out->Write("\t\t\t\t\t\t{{\n");
|
||||
out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_alpha[{}].direction = " I_LIGHTS
|
||||
"[light_index].dir.xyz;\n",
|
||||
channel_index, light_count_index);
|
||||
out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_alpha[{}].position = " I_LIGHTS
|
||||
"[light_index].pos.xyz;\n",
|
||||
channel_index, light_count_index);
|
||||
out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_alpha[{}].cosatt = " I_LIGHTS
|
||||
"[light_index].cosatt;\n",
|
||||
channel_index, light_count_index);
|
||||
out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_alpha[{}].distatt = " I_LIGHTS
|
||||
"[light_index].distatt;\n",
|
||||
channel_index, light_count_index);
|
||||
out->Write(
|
||||
"\t\t\t\t\t\t\tcustom_data.lights_chan{}_alpha[{}].attenuation_type = attnfunc;\n",
|
||||
channel_index, light_count_index);
|
||||
out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_alpha[{}].color = float3(" I_LIGHTS
|
||||
"[light_index].color.a) / float3(255.0, 255.0, 255.0);\n",
|
||||
channel_index, light_count_index);
|
||||
out->Write("\t\t\t\t\t\t\tcustom_data.light_chan{}_alpha_count += 1;\n", channel_index);
|
||||
out->Write("\t\t\t\t\t\t}}\n");
|
||||
}
|
||||
}
|
||||
|
||||
out->Write("\t\t\t\t\t}}\n");
|
||||
}
|
||||
out->Write("\t\t\t\t}}\n");
|
||||
out->Write("\t\t\t}}\n");
|
||||
out->Write("\t\t}}\n");
|
||||
}
|
||||
out->Write("\t}}\n");
|
||||
|
||||
for (u32 i = 0; i < 16; i++)
|
||||
{
|
||||
// Shader compilation complains if every struct isn't initialized
|
||||
|
||||
// Color Input
|
||||
for (u32 j = 0; j < 4; j++)
|
||||
{
|
||||
out->Write("\tcustom_data.tev_stages[{}].input_color[{}].input_type = "
|
||||
"CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_UNUSED;\n",
|
||||
i, j);
|
||||
out->Write("\tcustom_data.tev_stages[{}].input_color[{}].value = "
|
||||
"float3(0, 0, 0);\n",
|
||||
i, j);
|
||||
}
|
||||
|
||||
// Alpha Input
|
||||
for (u32 j = 0; j < 4; j++)
|
||||
{
|
||||
out->Write("\tcustom_data.tev_stages[{}].input_alpha[{}].input_type = "
|
||||
"CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_UNUSED;\n",
|
||||
i, j);
|
||||
out->Write("\tcustom_data.tev_stages[{}].input_alpha[{}].value = "
|
||||
"float(0);\n",
|
||||
i, j);
|
||||
}
|
||||
|
||||
// Texmap
|
||||
out->Write("\tcustom_data.tev_stages[{}].texmap = 0u;\n", i);
|
||||
|
||||
// Output
|
||||
out->Write("\tcustom_data.tev_stages[{}].output_color = "
|
||||
"float4(0, 0, 0, 0);\n",
|
||||
i);
|
||||
}
|
||||
|
||||
// Actual data will be filled out in the tev stage code, just set the
|
||||
// stage count for now
|
||||
out->Write("\tcustom_data.tev_stage_count = num_stages;\n");
|
||||
|
||||
// Time
|
||||
out->Write("\tcustom_data.time_ms = time_ms;\n");
|
||||
}
|
||||
} // namespace
|
||||
PixelShaderUid GetPixelShaderUid()
|
||||
{
|
||||
PixelShaderUid out;
|
||||
|
@ -56,7 +310,8 @@ void ClearUnusedPixelShaderUidBits(APIType api_type, const ShaderHostConfig& hos
|
|||
}
|
||||
|
||||
ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config,
|
||||
const pixel_ubershader_uid_data* uid_data)
|
||||
const pixel_ubershader_uid_data* uid_data,
|
||||
const CustomPixelShaderContents& custom_details)
|
||||
{
|
||||
const bool per_pixel_lighting = host_config.per_pixel_lighting;
|
||||
const bool msaa = host_config.msaa;
|
||||
|
@ -76,6 +331,12 @@ 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);
|
||||
WriteCustomShaderStructDef(&out, numTexgen);
|
||||
for (std::size_t i = 0; i < custom_details.shaders.size(); i++)
|
||||
{
|
||||
const auto& shader_details = custom_details.shaders[i];
|
||||
out.Write(fmt::runtime(shader_details.custom_shader), i);
|
||||
}
|
||||
if (per_pixel_lighting)
|
||||
WriteLightingFunction(out);
|
||||
|
||||
|
@ -228,6 +489,68 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config,
|
|||
}
|
||||
|
||||
out.Write("}}\n\n");
|
||||
|
||||
out.Write("uint selectTexCoordIndex(uint texmap)");
|
||||
out.Write("{{\n");
|
||||
|
||||
if (api_type == APIType::D3D)
|
||||
{
|
||||
out.Write(" switch (texmap) {{\n");
|
||||
for (u32 i = 0; i < numTexgen; i++)
|
||||
{
|
||||
out.Write(" case {}u:\n"
|
||||
" return {};\n",
|
||||
i, i);
|
||||
}
|
||||
out.Write(" default:\n"
|
||||
" return 0;\n"
|
||||
" }}\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
out.Write(" if (texmap >= {}u) {{\n", numTexgen);
|
||||
out.Write(" return 0;\n"
|
||||
" }}\n");
|
||||
if (numTexgen > 4)
|
||||
out.Write(" if (texmap < 4u) {{\n");
|
||||
if (numTexgen > 2)
|
||||
out.Write(" if (texmap < 2u) {{\n");
|
||||
if (numTexgen > 1)
|
||||
out.Write(" return (texmap == 0u) ? 0 : 1;\n");
|
||||
else
|
||||
out.Write(" return 0;\n");
|
||||
if (numTexgen > 2)
|
||||
{
|
||||
out.Write(" }} else {{\n"); // >= 2 < min(4, numTexgen)
|
||||
if (numTexgen > 3)
|
||||
out.Write(" return (texmap == 2u) ? 2 : 3;\n");
|
||||
else
|
||||
out.Write(" return 2;\n");
|
||||
out.Write(" }}\n");
|
||||
}
|
||||
if (numTexgen > 4)
|
||||
{
|
||||
out.Write(" }} else {{\n"); // >= 4 < min(8, numTexgen)
|
||||
if (numTexgen > 6)
|
||||
out.Write(" if (texmap < 6u) {{\n");
|
||||
if (numTexgen > 5)
|
||||
out.Write(" return (texmap == 4u) ? 4 : 5;\n");
|
||||
else
|
||||
out.Write(" return 4;\n");
|
||||
if (numTexgen > 6)
|
||||
{
|
||||
out.Write(" }} else {{\n"); // >= 6 < min(8, numTexgen)
|
||||
if (numTexgen > 7)
|
||||
out.Write(" return (texmap == 6u) ? 6 : 7;\n");
|
||||
else
|
||||
out.Write(" return 6;\n");
|
||||
out.Write(" }}\n");
|
||||
}
|
||||
out.Write(" }}\n");
|
||||
}
|
||||
}
|
||||
|
||||
out.Write("}}\n\n");
|
||||
}
|
||||
|
||||
// =====================
|
||||
|
@ -316,43 +639,43 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config,
|
|||
// TEV's Special Lerp
|
||||
// ======================
|
||||
const auto WriteTevLerp = [&out](std::string_view components) {
|
||||
out.Write(
|
||||
"// TEV's Linear Interpolate, plus bias, add/subtract and scale\n"
|
||||
"int{0} tevLerp{0}(int{0} A, int{0} B, int{0} C, int{0} D, uint bias, bool op, "
|
||||
"uint scale) {{\n"
|
||||
" // Scale C from 0..255 to 0..256\n"
|
||||
" C += C >> 7;\n"
|
||||
"\n"
|
||||
" // Add bias to D\n"
|
||||
" if (bias == 1u) D += 128;\n"
|
||||
" else if (bias == 2u) D -= 128;\n"
|
||||
"\n"
|
||||
" int{0} lerp = (A << 8) + (B - A)*C;\n"
|
||||
" if (scale != 3u) {{\n"
|
||||
" lerp = lerp << scale;\n"
|
||||
" D = D << scale;\n"
|
||||
" }}\n"
|
||||
"\n"
|
||||
" // TODO: Is this rounding bias still added when the scale is divide by 2? Currently we "
|
||||
"do not apply it.\n"
|
||||
" if (scale != 3u)\n"
|
||||
" lerp = lerp + (op ? 127 : 128);\n"
|
||||
"\n"
|
||||
" int{0} result = lerp >> 8;\n"
|
||||
"\n"
|
||||
" // Add/Subtract D\n"
|
||||
" if (op) // Subtract\n"
|
||||
" result = D - result;\n"
|
||||
" else // Add\n"
|
||||
" result = D + result;\n"
|
||||
"\n"
|
||||
" // Most of the Scale was moved inside the lerp for improved precision\n"
|
||||
" // But we still do the divide by 2 here\n"
|
||||
" if (scale == 3u)\n"
|
||||
" result = result >> 1;\n"
|
||||
" return result;\n"
|
||||
"}}\n\n",
|
||||
components);
|
||||
out.Write("// TEV's Linear Interpolate, plus bias, add/subtract and scale\n"
|
||||
"int{0} tevLerp{0}(int{0} A, int{0} B, int{0} C, int{0} D, uint bias, bool op, "
|
||||
"uint scale) {{\n"
|
||||
" // Scale C from 0..255 to 0..256\n"
|
||||
" C += C >> 7;\n"
|
||||
"\n"
|
||||
" // Add bias to D\n"
|
||||
" if (bias == 1u) D += 128;\n"
|
||||
" else if (bias == 2u) D -= 128;\n"
|
||||
"\n"
|
||||
" int{0} lerp = (A << 8) + (B - A)*C;\n"
|
||||
" if (scale != 3u) {{\n"
|
||||
" lerp = lerp << scale;\n"
|
||||
" D = D << scale;\n"
|
||||
" }}\n"
|
||||
"\n"
|
||||
" // TODO: Is this rounding bias still added when the scale is divide by 2? "
|
||||
"Currently we "
|
||||
"do not apply it.\n"
|
||||
" if (scale != 3u)\n"
|
||||
" lerp = lerp + (op ? 127 : 128);\n"
|
||||
"\n"
|
||||
" int{0} result = lerp >> 8;\n"
|
||||
"\n"
|
||||
" // Add/Subtract D\n"
|
||||
" if (op) // Subtract\n"
|
||||
" result = D - result;\n"
|
||||
" else // Add\n"
|
||||
" result = D + result;\n"
|
||||
"\n"
|
||||
" // Most of the Scale was moved inside the lerp for improved precision\n"
|
||||
" // But we still do the divide by 2 here\n"
|
||||
" if (scale == 3u)\n"
|
||||
" result = result >> 1;\n"
|
||||
" return result;\n"
|
||||
"}}\n\n",
|
||||
components);
|
||||
};
|
||||
WriteTevLerp(""); // int
|
||||
WriteTevLerp("3"); // int3
|
||||
|
@ -437,6 +760,25 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config,
|
|||
"return int3(0, 0, 0);", // ZERO
|
||||
};
|
||||
|
||||
static constexpr Common::EnumMap<std::string_view, TevColorArg::Zero> tev_c_input_type{
|
||||
"return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_PREV;",
|
||||
"return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_PREV;",
|
||||
"return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR;",
|
||||
"return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR;",
|
||||
"return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR;",
|
||||
"return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR;",
|
||||
"return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR;",
|
||||
"return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR;",
|
||||
"return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_TEX;",
|
||||
"return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_TEX;",
|
||||
"return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_RAS;",
|
||||
"return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_RAS;",
|
||||
"return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_NUMERIC;",
|
||||
"return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_NUMERIC;",
|
||||
"return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_KONST;",
|
||||
"return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_NUMERIC;",
|
||||
};
|
||||
|
||||
static constexpr Common::EnumMap<std::string_view, TevAlphaArg::Zero> tev_a_input_table{
|
||||
"return s.Reg[0].a;", // APREV,
|
||||
"return s.Reg[1].a;", // A0,
|
||||
|
@ -448,6 +790,17 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config,
|
|||
"return 0;", // ZERO
|
||||
};
|
||||
|
||||
static constexpr Common::EnumMap<std::string_view, TevAlphaArg::Zero> tev_a_input_type{
|
||||
"return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_PREV;",
|
||||
"return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR;",
|
||||
"return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR;",
|
||||
"return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR;",
|
||||
"return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_TEX;",
|
||||
"return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_RAS;",
|
||||
"return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_KONST;",
|
||||
"return CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_NUMERIC;",
|
||||
};
|
||||
|
||||
static constexpr Common::EnumMap<std::string_view, TevOutput::Color2> tev_regs_lookup_table{
|
||||
"return s.Reg[0];",
|
||||
"return s.Reg[1];",
|
||||
|
@ -489,6 +842,16 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config,
|
|||
out.Write("}}\n"
|
||||
"\n");
|
||||
|
||||
out.Write("// Helper function for Custom Shader Input Type\n"
|
||||
"uint getColorInputType(uint index) {{\n");
|
||||
WriteSwitch(out, api_type, "index", tev_c_input_type, 2, false);
|
||||
out.Write("}}\n"
|
||||
"\n"
|
||||
"uint getAlphaInputType(uint index) {{\n");
|
||||
WriteSwitch(out, api_type, "index", tev_a_input_type, 2, false);
|
||||
out.Write("}}\n"
|
||||
"\n");
|
||||
|
||||
// Since the fixed-point texture coodinate variables aren't global, we need to pass
|
||||
// them to the select function. This applies to all backends.
|
||||
if (numTexgen > 0)
|
||||
|
@ -505,6 +868,17 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config,
|
|||
out.Write("void main()\n{{\n");
|
||||
out.Write(" float4 rawpos = gl_FragCoord;\n");
|
||||
|
||||
out.Write(" uint num_stages = {};\n\n",
|
||||
BitfieldExtract<&GenMode::numtevstages>("bpmem_genmode"));
|
||||
|
||||
bool has_custom_shader_details = false;
|
||||
if (std::any_of(custom_details.shaders.begin(), custom_details.shaders.end(),
|
||||
[](const std::optional<CustomPixelShader>& ps) { return ps.has_value(); }))
|
||||
{
|
||||
WriteCustomShaderStructImpl(&out, numTexgen, per_pixel_lighting);
|
||||
has_custom_shader_details = true;
|
||||
}
|
||||
|
||||
if (use_framebuffer_fetch)
|
||||
{
|
||||
// Store off a copy of the initial framebuffer value.
|
||||
|
@ -563,9 +937,6 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config,
|
|||
" // o.colors_1 = float4(0.0, 0.0, 0.0, 0.0);\n");
|
||||
}
|
||||
|
||||
out.Write(" uint num_stages = {};\n\n",
|
||||
BitfieldExtract<&GenMode::numtevstages>("bpmem_genmode"));
|
||||
|
||||
out.Write(" // Main tev loop\n");
|
||||
|
||||
out.Write(" for(uint stage = 0u; stage <= num_stages; stage++)\n"
|
||||
|
@ -618,9 +989,9 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config,
|
|||
// indirect texture stage is enabled). If the matrix is off, the result doesn't matter; if the
|
||||
// indirect texture stage is disabled, the result is undefined (and produces a glitchy pattern
|
||||
// on hardware, different from this).
|
||||
// For the undefined case, we just skip applying the indirect operation, which is close enough.
|
||||
// Viewtiful Joe hits the undefined case (bug 12525).
|
||||
// Wrapping and add to previous still apply in this case (and when the stage is disabled).
|
||||
// For the undefined case, we just skip applying the indirect operation, which is close
|
||||
// enough. Viewtiful Joe hits the undefined case (bug 12525). Wrapping and add to previous
|
||||
// still apply in this case (and when the stage is disabled).
|
||||
out.Write(" if (bpmem_iref(bt) != 0u) {{\n");
|
||||
out.Write(" int3 indcoord;\n");
|
||||
LookupIndirectTexture("indcoord", "bt");
|
||||
|
@ -826,7 +1197,8 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config,
|
|||
" alpha_B = selectAlphaInput(s, ss, {0}colors_0, {0}colors_1, alpha_b) & 255;\n"
|
||||
" }};\n"
|
||||
" int alpha_C = selectAlphaInput(s, ss, {0}colors_0, {0}colors_1, alpha_c) & 255;\n"
|
||||
" int alpha_D = selectAlphaInput(s, ss, {0}colors_0, {0}colors_1, alpha_d); // 10 bits "
|
||||
" int alpha_D = selectAlphaInput(s, ss, {0}colors_0, {0}colors_1, alpha_d); // 10 "
|
||||
"bits "
|
||||
"+ sign\n"
|
||||
"\n", // TODO: do we need to sign extend?
|
||||
color_input_prefix);
|
||||
|
@ -857,9 +1229,81 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config,
|
|||
"\n"
|
||||
" // Write result to the correct input register of the next stage\n");
|
||||
WriteSwitch(out, api_type, "alpha_dest", tev_a_set_table, 6, true);
|
||||
out.Write(" }}\n"
|
||||
" }} // Main TEV loop\n"
|
||||
"\n");
|
||||
if (has_custom_shader_details)
|
||||
{
|
||||
for (u32 stage_index = 0; stage_index < 16; stage_index++)
|
||||
{
|
||||
out.Write("\tif (stage == {}u) {{\n", stage_index);
|
||||
// Color input
|
||||
out.Write("\t\tcustom_data.tev_stages[{}].input_color[0].value = color_A / float3(255.0, "
|
||||
"255.0, 255.0);\n",
|
||||
stage_index);
|
||||
out.Write("\t\tcustom_data.tev_stages[{}].input_color[0].input_type = "
|
||||
"getColorInputType(color_a);\n",
|
||||
stage_index);
|
||||
out.Write("\t\tcustom_data.tev_stages[{}].input_color[1].value = color_B / float3(255.0, "
|
||||
"255.0, 255.0);\n",
|
||||
stage_index);
|
||||
out.Write("\t\tcustom_data.tev_stages[{}].input_color[1].input_type = "
|
||||
"getColorInputType(color_b);\n",
|
||||
stage_index);
|
||||
out.Write("\t\tcustom_data.tev_stages[{}].input_color[2].value = color_C / float3(255.0, "
|
||||
"255.0, 255.0);\n",
|
||||
stage_index);
|
||||
out.Write("\t\tcustom_data.tev_stages[{}].input_color[2].input_type = "
|
||||
"getColorInputType(color_c);\n",
|
||||
stage_index);
|
||||
out.Write("\t\tcustom_data.tev_stages[{}].input_color[3].value = color_D / float3(255.0, "
|
||||
"255.0, 255.0);\n",
|
||||
stage_index);
|
||||
out.Write("\t\tcustom_data.tev_stages[{}].input_color[3].input_type = "
|
||||
"getColorInputType(color_c);\n",
|
||||
stage_index);
|
||||
|
||||
// Alpha input
|
||||
out.Write("\t\tcustom_data.tev_stages[{}].input_alpha[0].value = alpha_A / float(255.0);\n",
|
||||
stage_index);
|
||||
out.Write("\t\tcustom_data.tev_stages[{}].input_alpha[0].input_type = "
|
||||
"getAlphaInputType(alpha_a);\n",
|
||||
stage_index);
|
||||
out.Write("\t\tcustom_data.tev_stages[{}].input_alpha[1].value = alpha_B / float(255.0);\n",
|
||||
stage_index);
|
||||
out.Write("\t\tcustom_data.tev_stages[{}].input_alpha[1].input_type = "
|
||||
"getAlphaInputType(alpha_b);\n",
|
||||
stage_index);
|
||||
out.Write("\t\tcustom_data.tev_stages[{}].input_alpha[2].value = alpha_C / float(255.0);\n",
|
||||
stage_index);
|
||||
out.Write("\t\tcustom_data.tev_stages[{}].input_alpha[2].input_type = "
|
||||
"getAlphaInputType(alpha_c);\n",
|
||||
stage_index);
|
||||
out.Write("\t\tcustom_data.tev_stages[{}].input_alpha[3].value = alpha_D / float(255.0);\n",
|
||||
stage_index);
|
||||
out.Write("\t\tcustom_data.tev_stages[{}].input_alpha[3].input_type = "
|
||||
"getAlphaInputType(alpha_d);\n",
|
||||
stage_index);
|
||||
|
||||
if (numTexgen != 0)
|
||||
{
|
||||
// Texmap
|
||||
out.Write("\t\tif (texture_enabled) {{\n");
|
||||
out.Write("\t\t\tuint sampler_num = {};\n",
|
||||
BitfieldExtract<&TwoTevStageOrders::texmap_even>("ss.order"));
|
||||
out.Write("\t\tcustom_data.tev_stages[{}].texmap = sampler_num;\n", stage_index);
|
||||
out.Write("\t\t}}\n");
|
||||
}
|
||||
|
||||
// Output
|
||||
out.Write("\t\tcustom_data.tev_stages[{}].output_color.rgb = color / float3(255.0, 255.0, "
|
||||
"255.0);\n",
|
||||
stage_index);
|
||||
out.Write("\t\tcustom_data.tev_stages[{}].output_color.a = alpha / float(255.0);\n",
|
||||
stage_index);
|
||||
out.Write("\t}}\n");
|
||||
}
|
||||
}
|
||||
out.Write(" }}\n");
|
||||
out.Write(" }} // Main TEV loop\n");
|
||||
out.Write("\n");
|
||||
|
||||
// Select the output color and alpha registers from the last stage.
|
||||
out.Write(" int4 TevResult;\n");
|
||||
|
@ -942,8 +1386,8 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config,
|
|||
{
|
||||
// Instead of using discard, fetch the framebuffer's color value and use it as the output
|
||||
// for this fragment.
|
||||
out.Write(
|
||||
" #define discard_fragment {{ real_ocol0 = float4(initial_ocol0.xyz, 1.0); return; }}\n");
|
||||
out.Write(" #define discard_fragment {{ real_ocol0 = float4(initial_ocol0.xyz, 1.0); "
|
||||
"return; }}\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1109,8 +1553,8 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config,
|
|||
" }}\n");
|
||||
}
|
||||
|
||||
// Some backends require that the shader outputs be uint when writing to a uint render target for
|
||||
// logic op.
|
||||
// Some backends require that the shader outputs be uint when writing to a uint render target
|
||||
// for logic op.
|
||||
if (uid_data->uint_output)
|
||||
{
|
||||
out.Write(" if (bpmem_rgba6_format)\n"
|
||||
|
@ -1142,6 +1586,19 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config,
|
|||
}
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < custom_details.shaders.size(); i++)
|
||||
{
|
||||
const auto& shader_details = custom_details.shaders[i];
|
||||
|
||||
if (!shader_details.custom_shader.empty())
|
||||
{
|
||||
out.Write("\t{{\n");
|
||||
out.Write("\t\tcustom_data.final_color = ocol0;\n");
|
||||
out.Write("\t\tocol0.xyz = {}_{}(custom_data).xyz;\n", CUSTOM_PIXELSHADER_COLOR_FUNC, i);
|
||||
out.Write("\t}}\n\n");
|
||||
}
|
||||
}
|
||||
|
||||
if (bounding_box)
|
||||
{
|
||||
out.Write(" if (bpmem_bounding_box) {{\n"
|
||||
|
@ -1209,13 +1666,13 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config,
|
|||
WriteSwitch(out, api_type, "blend_dst_factor", blendDstFactor, 4, true);
|
||||
WriteSwitch(out, api_type, "blend_dst_factor_alpha", blendDstFactorAlpha, 4, true);
|
||||
|
||||
out.Write(
|
||||
" float4 blend_result;\n"
|
||||
" if (blend_subtract)\n"
|
||||
" blend_result.rgb = initial_ocol0.rgb * blend_dst.rgb - ocol0.rgb * blend_src.rgb;\n"
|
||||
" else\n"
|
||||
" blend_result.rgb = initial_ocol0.rgb * blend_dst.rgb + ocol0.rgb * "
|
||||
"blend_src.rgb;\n");
|
||||
out.Write(" float4 blend_result;\n"
|
||||
" if (blend_subtract)\n"
|
||||
" blend_result.rgb = initial_ocol0.rgb * blend_dst.rgb - ocol0.rgb * "
|
||||
"blend_src.rgb;\n"
|
||||
" else\n"
|
||||
" blend_result.rgb = initial_ocol0.rgb * blend_dst.rgb + ocol0.rgb * "
|
||||
"blend_src.rgb;\n");
|
||||
|
||||
out.Write(" if (blend_subtract_alpha)\n"
|
||||
" blend_result.a = initial_ocol0.a * blend_dst.a - ocol0.a * blend_src.a;\n"
|
||||
|
|
|
@ -29,7 +29,8 @@ using PixelShaderUid = ShaderUid<pixel_ubershader_uid_data>;
|
|||
PixelShaderUid GetPixelShaderUid();
|
||||
|
||||
ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config,
|
||||
const pixel_ubershader_uid_data* uid_data);
|
||||
const pixel_ubershader_uid_data* uid_data,
|
||||
const CustomPixelShaderContents& custom_details);
|
||||
|
||||
void EnumeratePixelShaderUids(const std::function<void(const PixelShaderUid&)>& callback);
|
||||
void ClearUnusedPixelShaderUidBits(APIType api_type, const ShaderHostConfig& host_config,
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
#include "Core/ConfigManager.h"
|
||||
#include "Core/DolphinAnalytics.h"
|
||||
#include "Core/HW/SystemTimers.h"
|
||||
#include "Core/System.h"
|
||||
|
||||
#include "VideoCommon/AbstractGfx.h"
|
||||
|
@ -23,12 +24,14 @@
|
|||
#include "VideoCommon/DataReader.h"
|
||||
#include "VideoCommon/FramebufferManager.h"
|
||||
#include "VideoCommon/GeometryShaderManager.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/CustomShaderCache.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModActionData.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModManager.h"
|
||||
#include "VideoCommon/IndexGenerator.h"
|
||||
#include "VideoCommon/NativeVertexFormat.h"
|
||||
#include "VideoCommon/OpcodeDecoding.h"
|
||||
#include "VideoCommon/PerfQueryBase.h"
|
||||
#include "VideoCommon/PixelShaderGen.h"
|
||||
#include "VideoCommon/PixelShaderManager.h"
|
||||
#include "VideoCommon/Statistics.h"
|
||||
#include "VideoCommon/TextureCacheBase.h"
|
||||
|
@ -105,7 +108,10 @@ VertexManagerBase::~VertexManagerBase() = default;
|
|||
bool VertexManagerBase::Initialize()
|
||||
{
|
||||
m_frame_end_event = AfterFrameEvent::Register([this] { OnEndFrame(); }, "VertexManagerBase");
|
||||
m_after_present_event = AfterPresentEvent::Register(
|
||||
[this](PresentInfo& pi) { m_ticks_elapsed = pi.emulated_timestamp; }, "VertexManagerBase");
|
||||
m_index_generator.Init();
|
||||
m_custom_shader_cache = std::make_unique<CustomShaderCache>();
|
||||
m_cpu_cull.Init();
|
||||
return true;
|
||||
}
|
||||
|
@ -523,10 +529,18 @@ void VertexManagerBase::Flush()
|
|||
auto& geometry_shader_manager = system.GetGeometryShaderManager();
|
||||
auto& vertex_shader_manager = system.GetVertexShaderManager();
|
||||
|
||||
if (g_ActiveConfig.bGraphicMods)
|
||||
{
|
||||
const double seconds_elapsed =
|
||||
static_cast<double>(m_ticks_elapsed) / SystemTimers::GetTicksPerSecond();
|
||||
pixel_shader_manager.constants.time_ms = seconds_elapsed * 1000;
|
||||
}
|
||||
|
||||
CalculateBinormals(VertexLoaderManager::GetCurrentVertexFormat());
|
||||
// Calculate ZSlope for zfreeze
|
||||
const auto used_textures = UsedTextures();
|
||||
std::vector<std::string> texture_names;
|
||||
std::vector<u32> texture_units;
|
||||
if (!m_cull_all)
|
||||
{
|
||||
if (!g_ActiveConfig.bGraphicMods)
|
||||
|
@ -543,7 +557,12 @@ void VertexManagerBase::Flush()
|
|||
const auto cache_entry = g_texture_cache->Load(TextureInfo::FromStage(i));
|
||||
if (cache_entry)
|
||||
{
|
||||
texture_names.push_back(cache_entry->texture_info_name);
|
||||
if (std::find(texture_names.begin(), texture_names.end(),
|
||||
cache_entry->texture_info_name) == texture_names.end())
|
||||
{
|
||||
texture_names.push_back(cache_entry->texture_info_name);
|
||||
texture_units.push_back(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -562,13 +581,24 @@ void VertexManagerBase::Flush()
|
|||
|
||||
if (!m_cull_all)
|
||||
{
|
||||
for (const auto& texture_name : texture_names)
|
||||
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++)
|
||||
{
|
||||
const std::string& texture_name = texture_names[i];
|
||||
const u32 texture_unit = texture_units[i];
|
||||
bool skip = false;
|
||||
GraphicsModActionData::DrawStarted draw_started{&skip};
|
||||
GraphicsModActionData::DrawStarted draw_started{texture_unit, &skip, &custom_pixel_shader};
|
||||
for (const auto& action : g_graphics_mod_manager->GetDrawStartedActions(texture_name))
|
||||
{
|
||||
action->OnDrawStarted(&draw_started);
|
||||
if (custom_pixel_shader)
|
||||
{
|
||||
custom_pixel_shader_contents.shaders.push_back(*custom_pixel_shader);
|
||||
custom_pixel_texture_names.push_back(texture_name);
|
||||
}
|
||||
custom_pixel_shader = std::nullopt;
|
||||
}
|
||||
if (skip == true)
|
||||
return;
|
||||
|
@ -610,7 +640,65 @@ void VertexManagerBase::Flush()
|
|||
UpdatePipelineObject();
|
||||
if (m_current_pipeline_object)
|
||||
{
|
||||
g_gfx->SetPipeline(m_current_pipeline_object);
|
||||
const AbstractPipeline* current_pipeline = m_current_pipeline_object;
|
||||
if (!custom_pixel_shader_contents.shaders.empty())
|
||||
{
|
||||
CustomShaderInstance custom_shaders;
|
||||
custom_shaders.pixel_contents = std::move(custom_pixel_shader_contents);
|
||||
|
||||
switch (g_ActiveConfig.iShaderCompilationMode)
|
||||
{
|
||||
case ShaderCompilationMode::Synchronous:
|
||||
case ShaderCompilationMode::AsynchronousSkipRendering:
|
||||
{
|
||||
if (auto pipeline = m_custom_shader_cache->GetPipelineAsync(
|
||||
m_current_pipeline_config, custom_shaders, m_current_pipeline_object->m_config))
|
||||
{
|
||||
current_pipeline = *pipeline;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ShaderCompilationMode::SynchronousUberShaders:
|
||||
{
|
||||
// D3D has issues compiling large custom ubershaders
|
||||
// use specialized shaders instead
|
||||
if (g_ActiveConfig.backend_info.api_type == APIType::D3D)
|
||||
{
|
||||
if (auto pipeline = m_custom_shader_cache->GetPipelineAsync(
|
||||
m_current_pipeline_config, custom_shaders, m_current_pipeline_object->m_config))
|
||||
{
|
||||
current_pipeline = *pipeline;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (auto pipeline = m_custom_shader_cache->GetPipelineAsync(
|
||||
m_current_uber_pipeline_config, custom_shaders,
|
||||
m_current_pipeline_object->m_config))
|
||||
{
|
||||
current_pipeline = *pipeline;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ShaderCompilationMode::AsynchronousUberShaders:
|
||||
{
|
||||
if (auto pipeline = m_custom_shader_cache->GetPipelineAsync(
|
||||
m_current_pipeline_config, custom_shaders, m_current_pipeline_object->m_config))
|
||||
{
|
||||
current_pipeline = *pipeline;
|
||||
}
|
||||
else if (auto uber_pipeline = m_custom_shader_cache->GetPipelineAsync(
|
||||
m_current_uber_pipeline_config, custom_shaders,
|
||||
m_current_pipeline_object->m_config))
|
||||
{
|
||||
current_pipeline = *uber_pipeline;
|
||||
}
|
||||
}
|
||||
break;
|
||||
};
|
||||
}
|
||||
g_gfx->SetPipeline(current_pipeline);
|
||||
if (PerfQueryBase::ShouldEmulate())
|
||||
g_perf_query->EnableQuery(bpmem.zcontrol.early_ztest ? PQG_ZCOMP_ZCOMPLOC : PQG_ZCOMP);
|
||||
|
||||
|
@ -1006,3 +1094,9 @@ void VertexManagerBase::OnEndFrame()
|
|||
// state changes the specialized shader will not take over.
|
||||
InvalidatePipelineObject();
|
||||
}
|
||||
|
||||
void VertexManagerBase::NotifyCustomShaderCacheOfHostChange(const ShaderHostConfig& host_config)
|
||||
{
|
||||
m_custom_shader_cache->SetHostConfig(host_config);
|
||||
m_custom_shader_cache->Reload();
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include "VideoCommon/ShaderCache.h"
|
||||
#include "VideoCommon/VideoEvents.h"
|
||||
|
||||
class CustomShaderCache;
|
||||
class DataReader;
|
||||
class NativeVertexFormat;
|
||||
class PointerWrap;
|
||||
|
@ -128,6 +129,7 @@ public:
|
|||
m_current_pipeline_object = nullptr;
|
||||
m_pipeline_config_changed = true;
|
||||
}
|
||||
void NotifyCustomShaderCacheOfHostChange(const ShaderHostConfig& host_config);
|
||||
|
||||
// Utility pipeline drawing (e.g. EFB copies, post-processing, UI).
|
||||
virtual void UploadUtilityUniforms(const void* uniforms, u32 uniforms_size);
|
||||
|
@ -230,7 +232,11 @@ private:
|
|||
std::vector<u32> m_scheduled_command_buffer_kicks;
|
||||
bool m_allow_background_execution = true;
|
||||
|
||||
std::unique_ptr<CustomShaderCache> m_custom_shader_cache;
|
||||
u64 m_ticks_elapsed;
|
||||
|
||||
Common::EventHook m_frame_end_event;
|
||||
Common::EventHook m_after_present_event;
|
||||
};
|
||||
|
||||
extern std::unique_ptr<VertexManagerBase> g_vertex_manager;
|
||||
|
|
|
@ -134,6 +134,7 @@ void VideoConfig::Refresh()
|
|||
|
||||
texture_filtering_mode = Config::Get(Config::GFX_ENHANCE_FORCE_TEXTURE_FILTERING);
|
||||
iMaxAnisotropy = Config::Get(Config::GFX_ENHANCE_MAX_ANISOTROPY);
|
||||
output_resampling_mode = Config::Get(Config::GFX_ENHANCE_OUTPUT_RESAMPLING);
|
||||
sPostProcessingShader = Config::Get(Config::GFX_ENHANCE_POST_SHADER);
|
||||
bForceTrueColor = Config::Get(Config::GFX_ENHANCE_FORCE_TRUE_COLOR);
|
||||
bDisableCopyFilter = Config::Get(Config::GFX_ENHANCE_DISABLE_COPY_FILTER);
|
||||
|
@ -353,6 +354,7 @@ void CheckForConfigChanges()
|
|||
{
|
||||
OSD::AddMessage("Video config changed, reloading shaders.", OSD::Duration::NORMAL);
|
||||
g_vertex_manager->InvalidatePipelineObject();
|
||||
g_vertex_manager->NotifyCustomShaderCacheOfHostChange(new_host_config);
|
||||
g_shader_cache->SetHostConfig(new_host_config);
|
||||
g_shader_cache->Reload();
|
||||
g_framebuffer_manager->RecompileShaders();
|
||||
|
|
|
@ -52,6 +52,17 @@ enum class TextureFilteringMode : int
|
|||
Linear,
|
||||
};
|
||||
|
||||
enum class OutputResamplingMode : int
|
||||
{
|
||||
Default,
|
||||
Bilinear,
|
||||
BSpline,
|
||||
MitchellNetravali,
|
||||
CatmullRom,
|
||||
SharpBilinear,
|
||||
AreaSampling,
|
||||
};
|
||||
|
||||
enum class ColorCorrectionRegion : int
|
||||
{
|
||||
SMPTE_NTSCM,
|
||||
|
@ -103,6 +114,7 @@ struct VideoConfig final
|
|||
bool bSSAA = false;
|
||||
int iEFBScale = 0;
|
||||
TextureFilteringMode texture_filtering_mode = TextureFilteringMode::Default;
|
||||
OutputResamplingMode output_resampling_mode = OutputResamplingMode::Default;
|
||||
int iMaxAnisotropy = 0;
|
||||
std::string sPostProcessingShader;
|
||||
bool bForceTrueColor = false;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue