Merge pull request #12443 from iwubcode/custom_pipeline_action_material_cubemap

VideoCommon: update custom pipeline action to support a variety of texture samplers, support for materials, and more!
This commit is contained in:
Mai 2024-01-26 12:39:37 -05:00 committed by GitHub
commit a553308775
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 224 additions and 163 deletions

View File

@ -13,6 +13,7 @@
#include "VideoCommon/Assets/MaterialAsset.h"
#include "VideoCommon/Assets/ShaderAsset.h"
#include "VideoCommon/Assets/TextureAsset.h"
#include "VideoCommon/RenderState.h"
namespace VideoCommon
{
@ -289,6 +290,7 @@ CustomAssetLibrary::LoadInfo DirectFilesystemAssetLibrary::LoadTexture(const Ass
}
else
{
data->m_sampler = RenderState::GetLinearSamplerState();
data->m_type = TextureData::Type::Type_Texture2D;
}

View File

@ -11,6 +11,7 @@
#include "Common/FileUtil.h"
#include "Common/Logging/Log.h"
#include "Common/StringUtil.h"
#include "Common/VariantUtil.h"
#include "Core/System.h"
#include "VideoCommon/AbstractGfx.h"
@ -175,20 +176,6 @@ std::vector<std::string> GlobalConflicts(std::string_view source)
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>
@ -267,186 +254,251 @@ void CustomPipelineAction::OnDrawStarted(GraphicsModActionData::DrawStarted* dra
if (!draw_started->custom_pixel_shader) [[unlikely]]
return;
if (!m_valid)
if (!draw_started->material_uniform_buffer) [[unlikely]]
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 (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& 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));
}
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)
if (!pass.m_pixel_material.m_asset ||
pass_config.m_pixel_material_asset != pass.m_pixel_material.m_asset->GetAssetId())
{
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)
{
m_last_generated_shader_code = ShaderCode{};
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{};
}
create->additional_dependencies->push_back(VideoCommon::CachedAsset<VideoCommon::CustomAsset>{
pass.m_pixel_shader.m_asset, pass.m_pixel_shader.m_asset->GetLastLoadedTime()});
const auto shader_data = pass.m_pixel_shader.m_asset->GetData();
if (!shader_data)
{
m_valid = false;
return;
}
m_texture_code_names.clear();
std::vector<VideoCommon::CachedAsset<VideoCommon::GameTextureAsset>> game_assets;
for (const auto& property : material_data->properties)
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 for texture '{}' has material asset '{}' that uses a "
"Custom pipeline, has material asset '{}' that uses a "
"code name of '{}' but that can't be found on shader asset '{}'!",
create->texture_name, pass.m_pixel_material.m_asset->GetAssetId(),
property.m_code_name, pass.m_pixel_shader.m_asset->GetAssetId());
m_valid = false;
pass.m_pixel_material.m_asset->GetAssetId(), property.m_code_name,
pass.m_pixel_shader.m_asset->GetAssetId());
return;
}
if (auto* value = std::get_if<std::string>(&property.m_value))
if (auto* texture_asset_id =
std::get_if<VideoCommon::CustomAssetLibrary::AssetID>(&property.m_value))
{
auto asset = loader.LoadGameTexture(*value, m_library);
if (asset)
if (*texture_asset_id != "")
{
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);
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);
}
}
// 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 (m_last_generated_shader_code.GetBuffer().empty())
{
if (game_texture.m_asset)
// 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++)
{
auto data = game_texture.m_asset->GetData();
if (data)
{
if (data->m_texture.m_slices.empty() || data->m_texture.m_slices[0].m_levels.empty())
{
ERROR_LOG_FMT(
VIDEO,
"Custom pipeline for texture '{}' has asset '{}' that does not have any texture data",
create->texture_name, game_texture.m_asset->GetAssetId());
m_valid = false;
}
else if (create->texture_width != data->m_texture.m_slices[0].m_levels[0].width ||
create->texture_height != data->m_texture.m_slices[0].m_levels[0].height)
{
ERROR_LOG_FMT(VIDEO,
"Custom pipeline for texture '{}' has asset '{}' that does not match "
"the width/height of the texture loaded. Texture {}x{} vs asset {}x{}",
create->texture_name, game_texture.m_asset->GetAssetId(),
create->texture_width, create->texture_height,
data->m_texture.m_slices[0].m_levels[0].width,
data->m_texture.m_slices[0].m_levels[0].height);
m_valid = false;
}
}
else
{
m_valid = false;
}
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);
}
// 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());
CustomPixelShader custom_pixel_shader;
custom_pixel_shader.custom_shader = m_last_generated_shader_code.GetBuffer();
custom_pixel_shader.material_uniform_block = m_last_generated_material_code.GetBuffer();
*draw_started->custom_pixel_shader = custom_pixel_shader;
*draw_started->material_uniform_buffer = m_material_data;
}

View File

@ -4,6 +4,7 @@
#pragma once
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
@ -33,7 +34,6 @@ public:
std::vector<PipelinePassPassDescription> pass_descriptions);
~CustomPipelineAction();
void OnDrawStarted(GraphicsModActionData::DrawStarted*) override;
void OnTextureCreate(GraphicsModActionData::TextureCreate*) override;
private:
std::shared_ptr<VideoCommon::CustomAssetLibrary> m_library;
@ -42,13 +42,20 @@ private:
{
VideoCommon::CachedAsset<VideoCommon::MaterialAsset> m_pixel_material;
VideoCommon::CachedAsset<VideoCommon::PixelShaderAsset> m_pixel_shader;
std::vector<VideoCommon::CachedAsset<VideoCommon::GameTextureAsset>> m_game_textures;
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;
bool m_valid = true;
std::vector<std::string> m_texture_code_names;
std::vector<u8> m_material_data;
};

View File

@ -11,6 +11,7 @@
#include "Common/CommonTypes.h"
#include "Common/Matrix.h"
#include "Common/SmallVector.h"
#include "VideoCommon/Assets/TextureAsset.h"
#include "VideoCommon/PixelShaderGen.h"
@ -18,7 +19,7 @@ namespace GraphicsModActionData
{
struct DrawStarted
{
u32 texture_unit;
const Common::SmallVector<u32, 8>& texture_units;
bool* skip;
std::optional<CustomPixelShader>* custom_pixel_shader;
std::span<u8>* material_uniform_buffer;

View File

@ -12,6 +12,7 @@
#include "Common/EnumMap.h"
#include "Common/Logging/Log.h"
#include "Common/MathUtil.h"
#include "Common/SmallVector.h"
#include "Core/ConfigManager.h"
#include "Core/DolphinAnalytics.h"
@ -554,7 +555,7 @@ void VertexManagerBase::Flush()
// Calculate ZSlope for zfreeze
const auto used_textures = UsedTextures();
std::vector<std::string> texture_names;
std::vector<u32> texture_units;
Common::SmallVector<u32, 8> texture_units;
if (!m_cull_all)
{
if (!g_ActiveConfig.bGraphicMods)
@ -599,20 +600,18 @@ void VertexManagerBase::Flush()
std::optional<CustomPixelShader> custom_pixel_shader;
std::vector<std::string> custom_pixel_texture_names;
std::span<u8> custom_pixel_shader_uniforms;
bool skip = false;
for (size_t i = 0; i < texture_names.size(); i++)
{
const std::string& texture_name = texture_names[i];
const u32 texture_unit = texture_units[i];
bool skip = false;
GraphicsModActionData::DrawStarted draw_started{texture_unit, &skip, &custom_pixel_shader,
GraphicsModActionData::DrawStarted draw_started{texture_units, &skip, &custom_pixel_shader,
&custom_pixel_shader_uniforms};
for (const auto& action : g_graphics_mod_manager->GetDrawStartedActions(texture_name))
for (const auto& action : g_graphics_mod_manager->GetDrawStartedActions(texture_names[i]))
{
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_texture_names.push_back(texture_names[i]);
}
custom_pixel_shader = std::nullopt;
}

View File

@ -191,12 +191,12 @@ vec4 custom_main( in CustomShaderData data )
### Reading a texture
The following shader displays the contents of the texture denoted in the shader asset as `MY_TEX`:
The following shader displays the contents of the texture denoted in the shader asset as `MY_TEX` with the first texture coordinate data:
```glsl
vec4 custom_main( in CustomShaderData data )
{
return texture(samp[MY_TEX_UNIT], MY_TEX_COORD);
return texture(samp_MY_TEX, TEX_COORD0);
}
```