VideoCommon: refactor the custom pipeline logic in the graphics mod action into a separate class, so it is reusable
This commit is contained in:
parent
e740e32562
commit
3e3967ff94
|
@ -692,6 +692,7 @@
|
||||||
<ClInclude Include="VideoCommon\GraphicsModSystem\Runtime\Actions\PrintAction.h" />
|
<ClInclude Include="VideoCommon\GraphicsModSystem\Runtime\Actions\PrintAction.h" />
|
||||||
<ClInclude Include="VideoCommon\GraphicsModSystem\Runtime\Actions\ScaleAction.h" />
|
<ClInclude Include="VideoCommon\GraphicsModSystem\Runtime\Actions\ScaleAction.h" />
|
||||||
<ClInclude Include="VideoCommon\GraphicsModSystem\Runtime\Actions\SkipAction.h" />
|
<ClInclude Include="VideoCommon\GraphicsModSystem\Runtime\Actions\SkipAction.h" />
|
||||||
|
<ClInclude Include="VideoCommon\GraphicsModSystem\Runtime\CustomPipeline.h" />
|
||||||
<ClInclude Include="VideoCommon\GraphicsModSystem\Runtime\CustomShaderCache.h" />
|
<ClInclude Include="VideoCommon\GraphicsModSystem\Runtime\CustomShaderCache.h" />
|
||||||
<ClInclude Include="VideoCommon\GraphicsModSystem\Runtime\FBInfo.h" />
|
<ClInclude Include="VideoCommon\GraphicsModSystem\Runtime\FBInfo.h" />
|
||||||
<ClInclude Include="VideoCommon\GraphicsModSystem\Runtime\GraphicsModAction.h" />
|
<ClInclude Include="VideoCommon\GraphicsModSystem\Runtime\GraphicsModAction.h" />
|
||||||
|
@ -1323,6 +1324,7 @@
|
||||||
<ClCompile Include="VideoCommon\GraphicsModSystem\Runtime\Actions\PrintAction.cpp" />
|
<ClCompile Include="VideoCommon\GraphicsModSystem\Runtime\Actions\PrintAction.cpp" />
|
||||||
<ClCompile Include="VideoCommon\GraphicsModSystem\Runtime\Actions\ScaleAction.cpp" />
|
<ClCompile Include="VideoCommon\GraphicsModSystem\Runtime\Actions\ScaleAction.cpp" />
|
||||||
<ClCompile Include="VideoCommon\GraphicsModSystem\Runtime\Actions\SkipAction.cpp" />
|
<ClCompile Include="VideoCommon\GraphicsModSystem\Runtime\Actions\SkipAction.cpp" />
|
||||||
|
<ClCompile Include="VideoCommon\GraphicsModSystem\Runtime\CustomPipeline.cpp" />
|
||||||
<ClCompile Include="VideoCommon\GraphicsModSystem\Runtime\CustomShaderCache.cpp" />
|
<ClCompile Include="VideoCommon\GraphicsModSystem\Runtime\CustomShaderCache.cpp" />
|
||||||
<ClCompile Include="VideoCommon\GraphicsModSystem\Runtime\FBInfo.cpp" />
|
<ClCompile Include="VideoCommon\GraphicsModSystem\Runtime\FBInfo.cpp" />
|
||||||
<ClCompile Include="VideoCommon\GraphicsModSystem\Runtime\GraphicsModActionFactory.cpp" />
|
<ClCompile Include="VideoCommon\GraphicsModSystem\Runtime\GraphicsModActionFactory.cpp" />
|
||||||
|
|
|
@ -85,6 +85,8 @@ add_library(videocommon
|
||||||
GraphicsModSystem/Runtime/Actions/ScaleAction.h
|
GraphicsModSystem/Runtime/Actions/ScaleAction.h
|
||||||
GraphicsModSystem/Runtime/Actions/SkipAction.cpp
|
GraphicsModSystem/Runtime/Actions/SkipAction.cpp
|
||||||
GraphicsModSystem/Runtime/Actions/SkipAction.h
|
GraphicsModSystem/Runtime/Actions/SkipAction.h
|
||||||
|
GraphicsModSystem/Runtime/CustomPipeline.cpp
|
||||||
|
GraphicsModSystem/Runtime/CustomPipeline.h
|
||||||
GraphicsModSystem/Runtime/CustomShaderCache.cpp
|
GraphicsModSystem/Runtime/CustomShaderCache.cpp
|
||||||
GraphicsModSystem/Runtime/CustomShaderCache.h
|
GraphicsModSystem/Runtime/CustomShaderCache.h
|
||||||
GraphicsModSystem/Runtime/FBInfo.cpp
|
GraphicsModSystem/Runtime/FBInfo.cpp
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
#include <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
|
|
||||||
|
@ -20,164 +21,12 @@
|
||||||
#include "VideoCommon/ShaderGenCommon.h"
|
#include "VideoCommon/ShaderGenCommon.h"
|
||||||
#include "VideoCommon/TextureCacheBase.h"
|
#include "VideoCommon/TextureCacheBase.h"
|
||||||
|
|
||||||
namespace
|
std::unique_ptr<CustomPipelineAction>
|
||||||
|
CustomPipelineAction::Create(std::shared_ptr<VideoCommon::CustomAssetLibrary> library)
|
||||||
{
|
{
|
||||||
bool IsQualifier(std::string_view value)
|
return std::make_unique<CustomPipelineAction>(std::move(library));
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
std::unique_ptr<CustomPipelineAction>
|
std::unique_ptr<CustomPipelineAction>
|
||||||
CustomPipelineAction::Create(const picojson::value& json_data,
|
CustomPipelineAction::Create(const picojson::value& json_data,
|
||||||
std::shared_ptr<VideoCommon::CustomAssetLibrary> library)
|
std::shared_ptr<VideoCommon::CustomAssetLibrary> library)
|
||||||
|
@ -236,16 +85,19 @@ CustomPipelineAction::Create(const picojson::value& json_data,
|
||||||
return std::make_unique<CustomPipelineAction>(std::move(library), std::move(pipeline_passes));
|
return std::make_unique<CustomPipelineAction>(std::move(library), std::move(pipeline_passes));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CustomPipelineAction::CustomPipelineAction(std::shared_ptr<VideoCommon::CustomAssetLibrary> library)
|
||||||
|
: m_library(std::move(library))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
CustomPipelineAction::CustomPipelineAction(
|
CustomPipelineAction::CustomPipelineAction(
|
||||||
std::shared_ptr<VideoCommon::CustomAssetLibrary> library,
|
std::shared_ptr<VideoCommon::CustomAssetLibrary> library,
|
||||||
std::vector<PipelinePassPassDescription> pass_descriptions)
|
std::vector<PipelinePassPassDescription> pass_descriptions)
|
||||||
: m_library(std::move(library)), m_passes_config(std::move(pass_descriptions))
|
: m_library(std::move(library)), m_passes_config(std::move(pass_descriptions))
|
||||||
{
|
{
|
||||||
m_passes.resize(m_passes_config.size());
|
m_pipeline_passes.resize(m_passes_config.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
CustomPipelineAction::~CustomPipelineAction() = default;
|
|
||||||
|
|
||||||
void CustomPipelineAction::OnDrawStarted(GraphicsModActionData::DrawStarted* draw_started)
|
void CustomPipelineAction::OnDrawStarted(GraphicsModActionData::DrawStarted* draw_started)
|
||||||
{
|
{
|
||||||
if (!draw_started) [[unlikely]]
|
if (!draw_started) [[unlikely]]
|
||||||
|
@ -254,251 +106,20 @@ void CustomPipelineAction::OnDrawStarted(GraphicsModActionData::DrawStarted* dra
|
||||||
if (!draw_started->custom_pixel_shader) [[unlikely]]
|
if (!draw_started->custom_pixel_shader) [[unlikely]]
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (!draw_started->material_uniform_buffer) [[unlikely]]
|
if (m_pipeline_passes.empty()) [[unlikely]]
|
||||||
return;
|
|
||||||
|
|
||||||
if (m_passes.empty()) [[unlikely]]
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto& loader = Core::System::GetInstance().GetCustomAssetLoader();
|
auto& loader = Core::System::GetInstance().GetCustomAssetLoader();
|
||||||
|
|
||||||
// For now assume a single pass
|
// For now assume a single pass
|
||||||
const auto& pass_config = m_passes_config[0];
|
const auto& pass_config = m_passes_config[0];
|
||||||
auto& pass = m_passes[0];
|
auto& pass = m_pipeline_passes[0];
|
||||||
|
|
||||||
if (!pass.m_pixel_material.m_asset ||
|
pass.UpdatePixelData(loader, m_library, draw_started->texture_units,
|
||||||
pass_config.m_pixel_material_asset != pass.m_pixel_material.m_asset->GetAssetId())
|
pass_config.m_pixel_material_asset);
|
||||||
{
|
|
||||||
pass.m_pixel_material.m_asset =
|
|
||||||
loader.LoadMaterial(pass_config.m_pixel_material_asset, m_library);
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto material_data = pass.m_pixel_material.m_asset->GetData();
|
|
||||||
if (!material_data)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::size_t max_material_data_size = 0;
|
|
||||||
if (pass.m_pixel_material.m_asset->GetLastLoadedTime() >
|
|
||||||
pass.m_pixel_material.m_cached_write_time)
|
|
||||||
{
|
|
||||||
m_last_generated_material_code = ShaderCode{};
|
|
||||||
pass.m_pixel_material.m_cached_write_time = pass.m_pixel_material.m_asset->GetLastLoadedTime();
|
|
||||||
std::size_t texture_count = 0;
|
|
||||||
for (const auto& property : material_data->properties)
|
|
||||||
{
|
|
||||||
max_material_data_size += VideoCommon::MaterialProperty::GetMemorySize(property);
|
|
||||||
VideoCommon::MaterialProperty::WriteAsShaderCode(m_last_generated_material_code, property);
|
|
||||||
if (auto* texture_asset_id =
|
|
||||||
std::get_if<VideoCommon::CustomAssetLibrary::AssetID>(&property.m_value))
|
|
||||||
{
|
|
||||||
texture_count++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m_material_data.resize(max_material_data_size);
|
|
||||||
pass.m_game_textures.resize(texture_count);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!pass.m_pixel_shader.m_asset ||
|
|
||||||
pass.m_pixel_shader.m_asset->GetLastLoadedTime() > pass.m_pixel_shader.m_cached_write_time ||
|
|
||||||
material_data->shader_asset != pass.m_pixel_shader.m_asset->GetAssetId())
|
|
||||||
{
|
|
||||||
pass.m_pixel_shader.m_asset = loader.LoadPixelShader(material_data->shader_asset, m_library);
|
|
||||||
pass.m_pixel_shader.m_cached_write_time = pass.m_pixel_shader.m_asset->GetLastLoadedTime();
|
|
||||||
|
|
||||||
m_last_generated_shader_code = ShaderCode{};
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto shader_data = pass.m_pixel_shader.m_asset->GetData();
|
|
||||||
if (!shader_data)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shader_data->m_properties.size() != material_data->properties.size())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
u8* material_buffer = m_material_data.data();
|
|
||||||
u32 sampler_index = 8;
|
|
||||||
for (std::size_t index = 0; index < material_data->properties.size(); index++)
|
|
||||||
{
|
|
||||||
auto& property = material_data->properties[index];
|
|
||||||
const auto shader_it = shader_data->m_properties.find(property.m_code_name);
|
|
||||||
if (shader_it == shader_data->m_properties.end())
|
|
||||||
{
|
|
||||||
ERROR_LOG_FMT(VIDEO,
|
|
||||||
"Custom pipeline, has material asset '{}' that uses a "
|
|
||||||
"code name of '{}' but that can't be found on shader asset '{}'!",
|
|
||||||
pass.m_pixel_material.m_asset->GetAssetId(), property.m_code_name,
|
|
||||||
pass.m_pixel_shader.m_asset->GetAssetId());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auto* texture_asset_id =
|
|
||||||
std::get_if<VideoCommon::CustomAssetLibrary::AssetID>(&property.m_value))
|
|
||||||
{
|
|
||||||
if (*texture_asset_id != "")
|
|
||||||
{
|
|
||||||
auto asset = loader.LoadGameTexture(*texture_asset_id, m_library);
|
|
||||||
if (!asset)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto& texture_asset = pass.m_game_textures[index];
|
|
||||||
if (!texture_asset ||
|
|
||||||
texture_asset->m_cached_asset.m_asset->GetLastLoadedTime() >
|
|
||||||
texture_asset->m_cached_asset.m_cached_write_time ||
|
|
||||||
*texture_asset_id != texture_asset->m_cached_asset.m_asset->GetAssetId())
|
|
||||||
{
|
|
||||||
if (!texture_asset)
|
|
||||||
{
|
|
||||||
texture_asset = PipelinePass::CachedTextureAsset{};
|
|
||||||
}
|
|
||||||
const auto loaded_time = asset->GetLastLoadedTime();
|
|
||||||
texture_asset->m_cached_asset = VideoCommon::CachedAsset<VideoCommon::GameTextureAsset>{
|
|
||||||
std::move(asset), loaded_time};
|
|
||||||
texture_asset->m_texture.reset();
|
|
||||||
|
|
||||||
if (std::holds_alternative<VideoCommon::ShaderProperty::Sampler2D>(
|
|
||||||
shader_it->second.m_default))
|
|
||||||
{
|
|
||||||
texture_asset->m_sampler_code =
|
|
||||||
fmt::format("SAMPLER_BINDING({}) uniform sampler2D samp_{};\n", sampler_index,
|
|
||||||
property.m_code_name);
|
|
||||||
texture_asset->m_define_code = fmt::format("#define HAS_{} 1\n", property.m_code_name);
|
|
||||||
}
|
|
||||||
else if (std::holds_alternative<VideoCommon::ShaderProperty::Sampler2DArray>(
|
|
||||||
shader_it->second.m_default))
|
|
||||||
{
|
|
||||||
texture_asset->m_sampler_code =
|
|
||||||
fmt::format("SAMPLER_BINDING({}) uniform sampler2DArray samp_{};\n", sampler_index,
|
|
||||||
property.m_code_name);
|
|
||||||
texture_asset->m_define_code = fmt::format("#define HAS_{} 1\n", property.m_code_name);
|
|
||||||
}
|
|
||||||
else if (std::holds_alternative<VideoCommon::ShaderProperty::SamplerCube>(
|
|
||||||
shader_it->second.m_default))
|
|
||||||
{
|
|
||||||
texture_asset->m_sampler_code =
|
|
||||||
fmt::format("SAMPLER_BINDING({}) uniform samplerCube samp_{};\n", sampler_index,
|
|
||||||
property.m_code_name);
|
|
||||||
texture_asset->m_define_code = fmt::format("#define HAS_{} 1\n", property.m_code_name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto texture_data = texture_asset->m_cached_asset.m_asset->GetData();
|
|
||||||
if (!texture_data)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (texture_asset->m_texture)
|
|
||||||
{
|
|
||||||
g_gfx->SetTexture(sampler_index, texture_asset->m_texture.get());
|
|
||||||
g_gfx->SetSamplerState(sampler_index, texture_data->m_sampler);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
AbstractTextureType texture_usage = AbstractTextureType::Texture_2DArray;
|
|
||||||
if (std::holds_alternative<VideoCommon::ShaderProperty::SamplerCube>(
|
|
||||||
shader_it->second.m_default))
|
|
||||||
{
|
|
||||||
texture_usage = AbstractTextureType::Texture_CubeMap;
|
|
||||||
}
|
|
||||||
else if (std::holds_alternative<VideoCommon::ShaderProperty::Sampler2D>(
|
|
||||||
shader_it->second.m_default))
|
|
||||||
{
|
|
||||||
texture_usage = AbstractTextureType::Texture_2D;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (texture_data->m_texture.m_slices.empty() ||
|
|
||||||
texture_data->m_texture.m_slices[0].m_levels.empty())
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto& first_slice = texture_data->m_texture.m_slices[0];
|
|
||||||
const TextureConfig texture_config(
|
|
||||||
first_slice.m_levels[0].width, first_slice.m_levels[0].height,
|
|
||||||
static_cast<u32>(first_slice.m_levels.size()),
|
|
||||||
static_cast<u32>(texture_data->m_texture.m_slices.size()), 1,
|
|
||||||
first_slice.m_levels[0].format, 0, texture_usage);
|
|
||||||
texture_asset->m_texture = g_gfx->CreateTexture(
|
|
||||||
texture_config, fmt::format("Custom shader texture '{}'", property.m_code_name));
|
|
||||||
for (std::size_t slice_index = 0; slice_index < texture_data->m_texture.m_slices.size();
|
|
||||||
slice_index++)
|
|
||||||
{
|
|
||||||
auto& slice = texture_data->m_texture.m_slices[slice_index];
|
|
||||||
for (u32 level_index = 0; level_index < static_cast<u32>(slice.m_levels.size());
|
|
||||||
++level_index)
|
|
||||||
{
|
|
||||||
auto& level = slice.m_levels[level_index];
|
|
||||||
texture_asset->m_texture->Load(level_index, level.width, level.height,
|
|
||||||
level.row_length, level.data.data(), level.data.size(),
|
|
||||||
static_cast<u32>(slice_index));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sampler_index++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
VideoCommon::MaterialProperty::WriteToMemory(material_buffer, property);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_last_generated_shader_code.GetBuffer().empty())
|
|
||||||
{
|
|
||||||
// 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& game_texture : pass.m_game_textures)
|
|
||||||
{
|
|
||||||
if (!game_texture)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
m_last_generated_shader_code.Write("{}", game_texture->m_sampler_code);
|
|
||||||
m_last_generated_shader_code.Write("{}", game_texture->m_define_code);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (std::size_t i = 0; i < draw_started->texture_units.size(); i++)
|
|
||||||
{
|
|
||||||
const auto& texture_unit = draw_started->texture_units[i];
|
|
||||||
m_last_generated_shader_code.Write(
|
|
||||||
"#define TEX_COORD{} data.texcoord[data.texmap_to_texcoord_index[{}]].xy\n", i,
|
|
||||||
texture_unit);
|
|
||||||
}
|
|
||||||
m_last_generated_shader_code.Write("{}", color_shader_data);
|
|
||||||
}
|
|
||||||
CustomPixelShader custom_pixel_shader;
|
CustomPixelShader custom_pixel_shader;
|
||||||
custom_pixel_shader.custom_shader = m_last_generated_shader_code.GetBuffer();
|
custom_pixel_shader.custom_shader = pass.m_last_generated_shader_code.GetBuffer();
|
||||||
custom_pixel_shader.material_uniform_block = m_last_generated_material_code.GetBuffer();
|
custom_pixel_shader.material_uniform_block = pass.m_last_generated_material_code.GetBuffer();
|
||||||
*draw_started->custom_pixel_shader = custom_pixel_shader;
|
*draw_started->custom_pixel_shader = custom_pixel_shader;
|
||||||
*draw_started->material_uniform_buffer = m_material_data;
|
*draw_started->material_uniform_buffer = pass.m_material_data;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,20 +4,14 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <optional>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <picojson.h>
|
#include <picojson.h>
|
||||||
|
|
||||||
#include "VideoCommon/AbstractTexture.h"
|
|
||||||
#include "VideoCommon/Assets/CustomAssetLibrary.h"
|
#include "VideoCommon/Assets/CustomAssetLibrary.h"
|
||||||
#include "VideoCommon/Assets/MaterialAsset.h"
|
#include "VideoCommon/GraphicsModSystem/Runtime/CustomPipeline.h"
|
||||||
#include "VideoCommon/Assets/ShaderAsset.h"
|
|
||||||
#include "VideoCommon/Assets/TextureAsset.h"
|
|
||||||
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h"
|
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h"
|
||||||
#include "VideoCommon/ShaderGenCommon.h"
|
|
||||||
|
|
||||||
class CustomPipelineAction final : public GraphicsModAction
|
class CustomPipelineAction final : public GraphicsModAction
|
||||||
{
|
{
|
||||||
|
@ -30,32 +24,15 @@ public:
|
||||||
static std::unique_ptr<CustomPipelineAction>
|
static std::unique_ptr<CustomPipelineAction>
|
||||||
Create(const picojson::value& json_data,
|
Create(const picojson::value& json_data,
|
||||||
std::shared_ptr<VideoCommon::CustomAssetLibrary> library);
|
std::shared_ptr<VideoCommon::CustomAssetLibrary> library);
|
||||||
|
static std::unique_ptr<CustomPipelineAction>
|
||||||
|
Create(std::shared_ptr<VideoCommon::CustomAssetLibrary> library);
|
||||||
|
explicit CustomPipelineAction(std::shared_ptr<VideoCommon::CustomAssetLibrary> library);
|
||||||
CustomPipelineAction(std::shared_ptr<VideoCommon::CustomAssetLibrary> library,
|
CustomPipelineAction(std::shared_ptr<VideoCommon::CustomAssetLibrary> library,
|
||||||
std::vector<PipelinePassPassDescription> pass_descriptions);
|
std::vector<PipelinePassPassDescription> pass_descriptions);
|
||||||
~CustomPipelineAction();
|
|
||||||
void OnDrawStarted(GraphicsModActionData::DrawStarted*) override;
|
void OnDrawStarted(GraphicsModActionData::DrawStarted*) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<VideoCommon::CustomAssetLibrary> m_library;
|
std::shared_ptr<VideoCommon::CustomAssetLibrary> m_library;
|
||||||
std::vector<PipelinePassPassDescription> m_passes_config;
|
std::vector<PipelinePassPassDescription> m_passes_config;
|
||||||
struct PipelinePass
|
std::vector<CustomPipeline> m_pipeline_passes;
|
||||||
{
|
|
||||||
VideoCommon::CachedAsset<VideoCommon::MaterialAsset> m_pixel_material;
|
|
||||||
VideoCommon::CachedAsset<VideoCommon::PixelShaderAsset> m_pixel_shader;
|
|
||||||
|
|
||||||
struct CachedTextureAsset
|
|
||||||
{
|
|
||||||
VideoCommon::CachedAsset<VideoCommon::GameTextureAsset> m_cached_asset;
|
|
||||||
std::unique_ptr<AbstractTexture> m_texture;
|
|
||||||
std::string m_sampler_code;
|
|
||||||
std::string m_define_code;
|
|
||||||
};
|
|
||||||
std::vector<std::optional<CachedTextureAsset>> m_game_textures;
|
|
||||||
};
|
|
||||||
std::vector<PipelinePass> m_passes;
|
|
||||||
|
|
||||||
ShaderCode m_last_generated_shader_code;
|
|
||||||
ShaderCode m_last_generated_material_code;
|
|
||||||
|
|
||||||
std::vector<u8> m_material_data;
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,406 @@
|
||||||
|
// Copyright 2024 Dolphin Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "VideoCommon/GraphicsModSystem/Runtime/CustomPipeline.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
#include "Common/Logging/Log.h"
|
||||||
|
#include "Common/VariantUtil.h"
|
||||||
|
|
||||||
|
#include "VideoCommon/AbstractGfx.h"
|
||||||
|
#include "VideoCommon/Assets/CustomAssetLoader.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void CustomPipeline::UpdatePixelData(
|
||||||
|
VideoCommon::CustomAssetLoader& loader,
|
||||||
|
std::shared_ptr<VideoCommon::CustomAssetLibrary> library, std::span<const u32> texture_units,
|
||||||
|
const VideoCommon::CustomAssetLibrary::AssetID& material_to_load)
|
||||||
|
{
|
||||||
|
if (!m_pixel_material.m_asset || material_to_load != m_pixel_material.m_asset->GetAssetId())
|
||||||
|
{
|
||||||
|
m_pixel_material.m_asset = loader.LoadMaterial(material_to_load, library);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto material_data = m_pixel_material.m_asset->GetData();
|
||||||
|
if (!material_data)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t max_material_data_size = 0;
|
||||||
|
if (m_pixel_material.m_asset->GetLastLoadedTime() > m_pixel_material.m_cached_write_time)
|
||||||
|
{
|
||||||
|
m_last_generated_material_code = ShaderCode{};
|
||||||
|
m_pixel_material.m_cached_write_time = m_pixel_material.m_asset->GetLastLoadedTime();
|
||||||
|
std::size_t texture_count = 0;
|
||||||
|
for (const auto& property : material_data->properties)
|
||||||
|
{
|
||||||
|
max_material_data_size += VideoCommon::MaterialProperty::GetMemorySize(property);
|
||||||
|
VideoCommon::MaterialProperty::WriteAsShaderCode(m_last_generated_material_code, property);
|
||||||
|
if (auto* texture_asset_id =
|
||||||
|
std::get_if<VideoCommon::CustomAssetLibrary::AssetID>(&property.m_value))
|
||||||
|
{
|
||||||
|
texture_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_material_data.resize(max_material_data_size);
|
||||||
|
m_game_textures.resize(texture_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_pixel_shader.m_asset ||
|
||||||
|
m_pixel_shader.m_asset->GetLastLoadedTime() > m_pixel_shader.m_cached_write_time ||
|
||||||
|
material_data->shader_asset != m_pixel_shader.m_asset->GetAssetId())
|
||||||
|
{
|
||||||
|
m_pixel_shader.m_asset = loader.LoadPixelShader(material_data->shader_asset, library);
|
||||||
|
m_pixel_shader.m_cached_write_time = m_pixel_shader.m_asset->GetLastLoadedTime();
|
||||||
|
|
||||||
|
m_last_generated_shader_code = ShaderCode{};
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto shader_data = m_pixel_shader.m_asset->GetData();
|
||||||
|
if (!shader_data)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shader_data->m_properties.size() != material_data->properties.size())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
u8* material_buffer = m_material_data.data();
|
||||||
|
u32 sampler_index = 8;
|
||||||
|
for (std::size_t index = 0; index < material_data->properties.size(); index++)
|
||||||
|
{
|
||||||
|
auto& property = material_data->properties[index];
|
||||||
|
const auto shader_it = shader_data->m_properties.find(property.m_code_name);
|
||||||
|
if (shader_it == shader_data->m_properties.end())
|
||||||
|
{
|
||||||
|
ERROR_LOG_FMT(VIDEO,
|
||||||
|
"Custom pipeline, has material asset '{}' that uses a "
|
||||||
|
"code name of '{}' but that can't be found on shader asset '{}'!",
|
||||||
|
m_pixel_material.m_asset->GetAssetId(), property.m_code_name,
|
||||||
|
m_pixel_shader.m_asset->GetAssetId());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto* texture_asset_id =
|
||||||
|
std::get_if<VideoCommon::CustomAssetLibrary::AssetID>(&property.m_value))
|
||||||
|
{
|
||||||
|
if (*texture_asset_id != "")
|
||||||
|
{
|
||||||
|
auto asset = loader.LoadGameTexture(*texture_asset_id, library);
|
||||||
|
if (!asset)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& texture_asset = m_game_textures[index];
|
||||||
|
if (!texture_asset ||
|
||||||
|
texture_asset->m_cached_asset.m_asset->GetLastLoadedTime() >
|
||||||
|
texture_asset->m_cached_asset.m_cached_write_time ||
|
||||||
|
*texture_asset_id != texture_asset->m_cached_asset.m_asset->GetAssetId())
|
||||||
|
{
|
||||||
|
if (!texture_asset)
|
||||||
|
{
|
||||||
|
texture_asset = CachedTextureAsset{};
|
||||||
|
}
|
||||||
|
const auto loaded_time = asset->GetLastLoadedTime();
|
||||||
|
texture_asset->m_cached_asset = VideoCommon::CachedAsset<VideoCommon::GameTextureAsset>{
|
||||||
|
std::move(asset), loaded_time};
|
||||||
|
texture_asset->m_texture.reset();
|
||||||
|
|
||||||
|
if (std::holds_alternative<VideoCommon::ShaderProperty::Sampler2D>(
|
||||||
|
shader_it->second.m_default))
|
||||||
|
{
|
||||||
|
texture_asset->m_sampler_code =
|
||||||
|
fmt::format("SAMPLER_BINDING({}) uniform sampler2D samp_{};\n", sampler_index,
|
||||||
|
property.m_code_name);
|
||||||
|
texture_asset->m_define_code = fmt::format("#define HAS_{} 1\n", property.m_code_name);
|
||||||
|
}
|
||||||
|
else if (std::holds_alternative<VideoCommon::ShaderProperty::Sampler2DArray>(
|
||||||
|
shader_it->second.m_default))
|
||||||
|
{
|
||||||
|
texture_asset->m_sampler_code =
|
||||||
|
fmt::format("SAMPLER_BINDING({}) uniform sampler2DArray samp_{};\n", sampler_index,
|
||||||
|
property.m_code_name);
|
||||||
|
texture_asset->m_define_code = fmt::format("#define HAS_{} 1\n", property.m_code_name);
|
||||||
|
}
|
||||||
|
else if (std::holds_alternative<VideoCommon::ShaderProperty::SamplerCube>(
|
||||||
|
shader_it->second.m_default))
|
||||||
|
{
|
||||||
|
texture_asset->m_sampler_code =
|
||||||
|
fmt::format("SAMPLER_BINDING({}) uniform samplerCube samp_{};\n", sampler_index,
|
||||||
|
property.m_code_name);
|
||||||
|
texture_asset->m_define_code = fmt::format("#define HAS_{} 1\n", property.m_code_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto texture_data = texture_asset->m_cached_asset.m_asset->GetData();
|
||||||
|
if (!texture_data)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (texture_asset->m_texture)
|
||||||
|
{
|
||||||
|
g_gfx->SetTexture(sampler_index, texture_asset->m_texture.get());
|
||||||
|
g_gfx->SetSamplerState(sampler_index, RenderState::GetLinearSamplerState());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AbstractTextureType texture_type = AbstractTextureType::Texture_2DArray;
|
||||||
|
if (std::holds_alternative<VideoCommon::ShaderProperty::SamplerCube>(
|
||||||
|
shader_it->second.m_default))
|
||||||
|
{
|
||||||
|
texture_type = AbstractTextureType::Texture_CubeMap;
|
||||||
|
}
|
||||||
|
else if (std::holds_alternative<VideoCommon::ShaderProperty::Sampler2D>(
|
||||||
|
shader_it->second.m_default))
|
||||||
|
{
|
||||||
|
texture_type = AbstractTextureType::Texture_2D;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (texture_data->m_texture.m_slices.empty() ||
|
||||||
|
texture_data->m_texture.m_slices[0].m_levels.empty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& first_slice = texture_data->m_texture.m_slices[0];
|
||||||
|
const TextureConfig texture_config(
|
||||||
|
first_slice.m_levels[0].width, first_slice.m_levels[0].height,
|
||||||
|
static_cast<u32>(first_slice.m_levels.size()),
|
||||||
|
static_cast<u32>(texture_data->m_texture.m_slices.size()), 1,
|
||||||
|
first_slice.m_levels[0].format, 0, texture_type);
|
||||||
|
texture_asset->m_texture = g_gfx->CreateTexture(
|
||||||
|
texture_config, fmt::format("Custom shader texture '{}'", property.m_code_name));
|
||||||
|
for (std::size_t slice_index = 0; slice_index < texture_data->m_texture.m_slices.size();
|
||||||
|
slice_index++)
|
||||||
|
{
|
||||||
|
auto& slice = texture_data->m_texture.m_slices[slice_index];
|
||||||
|
for (u32 level_index = 0; level_index < static_cast<u32>(slice.m_levels.size());
|
||||||
|
++level_index)
|
||||||
|
{
|
||||||
|
auto& level = slice.m_levels[level_index];
|
||||||
|
texture_asset->m_texture->Load(level_index, level.width, level.height,
|
||||||
|
level.row_length, level.data.data(), level.data.size(),
|
||||||
|
static_cast<u32>(slice_index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sampler_index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
VideoCommon::MaterialProperty::WriteToMemory(material_buffer, property);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_last_generated_shader_code.GetBuffer().empty())
|
||||||
|
{
|
||||||
|
// 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& game_texture : m_game_textures)
|
||||||
|
{
|
||||||
|
if (!game_texture)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
m_last_generated_shader_code.Write("{}", game_texture->m_sampler_code);
|
||||||
|
m_last_generated_shader_code.Write("{}", game_texture->m_define_code);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (std::size_t i = 0; i < texture_units.size(); i++)
|
||||||
|
{
|
||||||
|
const auto& texture_unit = texture_units[i];
|
||||||
|
m_last_generated_shader_code.Write(
|
||||||
|
"#define TEX_COORD{} data.texcoord[data.texmap_to_texcoord_index[{}]].xy\n", i,
|
||||||
|
texture_unit);
|
||||||
|
}
|
||||||
|
m_last_generated_shader_code.Write("{}", color_shader_data);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
// Copyright 2024 Dolphin Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <span>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#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/ShaderGenCommon.h"
|
||||||
|
|
||||||
|
namespace VideoCommon
|
||||||
|
{
|
||||||
|
class CustomAssetLoader;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CustomPipeline
|
||||||
|
{
|
||||||
|
void UpdatePixelData(VideoCommon::CustomAssetLoader& loader,
|
||||||
|
std::shared_ptr<VideoCommon::CustomAssetLibrary> library,
|
||||||
|
std::span<const u32> texture_units,
|
||||||
|
const VideoCommon::CustomAssetLibrary::AssetID& material_to_load);
|
||||||
|
|
||||||
|
VideoCommon::CachedAsset<VideoCommon::MaterialAsset> m_pixel_material;
|
||||||
|
VideoCommon::CachedAsset<VideoCommon::PixelShaderAsset> m_pixel_shader;
|
||||||
|
|
||||||
|
struct CachedTextureAsset
|
||||||
|
{
|
||||||
|
VideoCommon::CachedAsset<VideoCommon::GameTextureAsset> m_cached_asset;
|
||||||
|
std::unique_ptr<AbstractTexture> m_texture;
|
||||||
|
std::string m_sampler_code;
|
||||||
|
std::string m_define_code;
|
||||||
|
};
|
||||||
|
std::vector<std::optional<CachedTextureAsset>> m_game_textures;
|
||||||
|
|
||||||
|
ShaderCode m_last_generated_shader_code;
|
||||||
|
ShaderCode m_last_generated_material_code;
|
||||||
|
|
||||||
|
std::vector<u8> m_material_data;
|
||||||
|
};
|
Loading…
Reference in New Issue