Large diffs are not rendered by default.

@@ -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);
}
144 changes: 144 additions & 0 deletions Source/Core/VideoCommon/GraphicsModSystem/Runtime/CustomShaderCache.h
@@ -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;
};
Expand Up @@ -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
Expand Down
Expand Up @@ -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"
Expand All @@ -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")
{
Expand All @@ -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;
}
Expand Down
Expand Up @@ -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);
}
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
{
Expand All @@ -208,19 +212,41 @@ 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)
{
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;
Expand Down
177 changes: 177 additions & 0 deletions Source/Core/VideoCommon/LightingShaderGen.cpp
Expand Up @@ -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);
}
}
4 changes: 4 additions & 0 deletions Source/Core/VideoCommon/LightingShaderGen.h
Expand Up @@ -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);
237 changes: 232 additions & 5 deletions Source/Core/VideoCommon/PixelShaderGen.cpp
Expand Up @@ -3,6 +3,7 @@

#include "VideoCommon/PixelShaderGen.h"

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <fmt/format.h>
Expand Down Expand Up @@ -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,
Expand All @@ -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)",
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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,
Expand All @@ -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;

Expand All @@ -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");

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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);
}

{
Expand Down Expand Up @@ -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);
Expand All @@ -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;

Expand Down Expand Up @@ -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,
Expand Down
6 changes: 5 additions & 1 deletion Source/Core/VideoCommon/PixelShaderGen.h
Expand Up @@ -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,
Expand Down
4 changes: 2 additions & 2 deletions Source/Core/VideoCommon/ShaderCache.cpp
Expand Up @@ -449,15 +449,15 @@ 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());
}

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()));
}
Expand Down
88 changes: 88 additions & 0 deletions Source/Core/VideoCommon/ShaderGenCommon.cpp
Expand Up @@ -10,6 +10,7 @@
#include "Core/ConfigManager.h"
#include "VideoCommon/VideoCommon.h"
#include "VideoCommon/VideoConfig.h"
#include "VideoCommon/XFMemory.h"

ShaderHostConfig ShaderHostConfig::GetCurrent()
{
Expand Down Expand Up @@ -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 = {};\n",
static_cast<u32>(AttenuationFunc::None));
out->Write("const uint CUSTOM_SHADER_LIGHTING_ATTENUATION_TYPE_POINT = {};\n",
static_cast<u32>(AttenuationFunc::Spec));
out->Write("const uint CUSTOM_SHADER_LIGHTING_ATTENUATION_TYPE_DIR = {};\n",
static_cast<u32>(AttenuationFunc::Dir));
out->Write("const uint CUSTOM_SHADER_LIGHTING_ATTENUATION_TYPE_SPOT = {};\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 = 0;\n");
out->Write("const uint CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR = 1;\n");
out->Write("const uint CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_TEX = 2;\n");
out->Write("const uint CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_RAS = 3;\n");
out->Write("const uint CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_KONST = 4;\n");
out->Write("const uint CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_NUMERIC = 5;\n");
out->Write("const uint CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_UNUSED = 6;\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");
}
19 changes: 19 additions & 0 deletions Source/Core/VideoCommon/ShaderGenCommon.h
Expand Up @@ -7,6 +7,7 @@
#include <functional>
#include <iterator>
#include <string>
#include <string_view>
#include <type_traits>
#include <vector>

Expand Down Expand Up @@ -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);
575 changes: 516 additions & 59 deletions Source/Core/VideoCommon/UberShaderPixel.cpp

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion Source/Core/VideoCommon/UberShaderPixel.h
Expand Up @@ -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,
Expand Down
102 changes: 98 additions & 4 deletions Source/Core/VideoCommon/VertexManagerBase.cpp
Expand Up @@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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)
Expand All @@ -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);
}
}
}
}
Expand All @@ -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;
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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();
}
6 changes: 6 additions & 0 deletions Source/Core/VideoCommon/VertexManagerBase.h
Expand Up @@ -15,6 +15,7 @@
#include "VideoCommon/ShaderCache.h"
#include "VideoCommon/VideoEvents.h"

class CustomShaderCache;
class DataReader;
class NativeVertexFormat;
class PointerWrap;
Expand Down Expand Up @@ -128,6 +129,7 @@ class VertexManagerBase
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);
Expand Down Expand Up @@ -230,7 +232,11 @@ class VertexManagerBase
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;
1 change: 1 addition & 0 deletions Source/Core/VideoCommon/VideoConfig.cpp
Expand Up @@ -353,6 +353,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();
Expand Down
251 changes: 251 additions & 0 deletions docs/CustomPipelineGraphicsMod.md
@@ -0,0 +1,251 @@
# Dolphin Custom Pipeline Specification

Dolphin provides content creators a way to overwrite its internal graphics pipeline data using graphics mods. At the moment, this supports modifying only the pixel shader. This document will describe the specification and give some examples.

## Graphics mod metadata format

This feature is powered by graphics mods. This document assumes the user is familiar with them and will only detail the action specific data needed to trigger this capability.

The action type for this feature is `custom_pipeline`. This action has the following data:

|Identifier |Required | Since |
|-------------------------|---------|-------|
|``passes`` | **Yes** | v1 |

`passes` is an array of pass blobs. Note that at the moment, Dolphin only supports a single pass. Each pass can have the following data:

|Identifier |Required | Since |
|-------------------------|---------|-------|
|``pixel_material_asset`` | **Yes** | v1 |

Here `pixel_material_asset` is the name of a material asset.

A full example is given below:

```json
{
"assets": [
{
"name": "material_replace_normal",
"data":
{
"": "normal.material.json"
}
},
{
"name": "shader_replace_normal",
"data":
{
"metadata": "replace_normal.shader.json",
"shader": "replace_normal.glsl"
}
},
{
"name": "normal_texture",
"data":
{
"": "normal_texture.png"
}
}
],
"features": [
{
"action": "custom_pipeline",
"action_data": {
"passes": [
{
"pixel_material_asset": "material_replace_normal"
}
]
},
"group": "PipelineTarget"
}
],
"groups": [
{
"name": "PipelineTarget",
"targets": [
{
"texture_filename": "tex1_512x512_m_afdbe7efg332229e_14",
"type": "draw_started"
},
{
"texture_filename": "tex1_512x512_m_afdbe7efg332229e_14",
"type": "create_texture"
}
]
}
]
}
```

## The shader format

The shaders are written in GLSL and converted to the target shader that the backend uses internally. The user is expected to provide an entrypoint with the following signature:

```
vec4 custom_main( in CustomShaderData data )
```

`CustomShaderData` encompasses all the data that Dolphin will pass to the user (in addition to the `samp` variable outlined above which is how textures are accessed). It has the following structure:

|Name | Type | Since | Description |
|-----------------------------|-------------------------|-------|-----------------------------------------------------------------------------------------------|
|``position`` | vec3 | v1 | The position of this pixel in _view space_ |
|``normal`` | vec3 | v1 | The normal of this pixel in _view space_ |
|``texcoord`` | vec3[] | v1 | An array of texture coordinates, the amount available is specified by ``texcoord_count`` |
|``texcoord_count`` | uint | v1 | The count of texture coordinates |
|``texmap_to_texcoord_index`` | uint[] | v1 | An array of texture units to texture coordinate values |
|``lights_chan0_color`` | CustomShaderLightData[] | v1 | An array of color lights for channel 0, the amount is specified by ``light_chan0_color_count``|
|``lights_chan0_alpha`` | CustomShaderLightData[] | v1 | An array of alpha lights for channel 0, the amount is specified by ``light_chan0_alpha_count``|
|``lights_chan1_color`` | CustomShaderLightData[] | v1 | An array of color lights for channel 1, the amount is specified by ``light_chan1_color_count``|
|``lights_chan1_alpha`` | CustomShaderLightData[] | v1 | An array of alpha lights for channel 1, the amount is specified by ``light_chan1_alpha_count``|
|``ambient_lighting`` | vec4[] | v1 | An array of ambient lighting values. Count is two, one for each color channel |
|``base_material`` | vec4[] | v1 | An array of the base material values. Count is two, one for each color channel |
|``tev_stages`` | CustomShaderTevStage[] | v1 | An array of TEV stages, the amount is specified by ``tev_stage_count`` |
|``tev_stage_count`` | uint | v1 | The count of TEV stages |
|``final_color`` | vec4 | v1 | The final color generated by Dolphin after all TEV stages are executed |
|``time_ms`` | uint | v1 | The time that has passed in milliseconds, since the game was started. Useful for animating |

`CustomShaderLightData` is used to denote lighting data the game is applying when rendering the specific draw call. It has the following structure:

|Name | Type | Since | Description |
|-------------------------|-------------------------|-------|-------------------------------------------------------------------------------------------------|
|``position`` | vec3 | v1 | The position of the light in _view space_ |
|``direction`` | vec3 | v1 | The direction in _view space_ the light is pointing (only applicable for point and spot lights) |
|``color`` | vec3 | v1 | The color of the light |
|``attenuation_type`` | uint | v1 | The attentuation type of the light. See details below |
|``cosatt`` | vec4 | v1 | The cos attenuation values used |
|``distatt`` | vec4 | v1 | The distance attenuation values used |

The `attenuation_type` is defined as a `uint` but is effecitvely an enumeration. It has the following values:

|Name | Since | Description |
|--------------------------------------------------|-------|-------------------------------------------------------------------------|
|``CUSTOM_SHADER_LIGHTING_ATTENUATION_TYPE_POINT`` | v1 | This value denotes the lighting attentuation is for a point light |
|``CUSTOM_SHADER_LIGHTING_ATTENUATION_TYPE_DIR`` | v1 | This value denotes the lighting attentuation is for a directional light |
|``CUSTOM_SHADER_LIGHTING_ATTENUATION_TYPE_SPOT`` | v1 | This value denotes the lighting attentuation is for a directional light |


`CustomShaderTevStage` is used to denote the various TEV operations. Each operation describes a graphical operation that the game is applying when rendering the specific draw call. It has the following structure:

|Name | Type | Since | Description |
|-------------------------|----------------------------------|-------|-------------------------------------------------------------------------------|
|``input_color`` | CustomShaderTevStageInputColor[] | v1 | The four color inputs that are used to produce the final output of this stage |
|``input_alpha`` | CustomShaderTevStageInputAlpha[] | v1 | The four alpha inputs that are used to produce the final output of this stage |
|``texmap`` | uint | v1 | The texture unit for this stage |
|``output_color`` | vec4 | v1 | The final output color this stage produces |


`CustomShaderTevStageInputColor` is a single input TEV operation for a color value. It has the following structure:

|Name | Type | Since | Description |
|-------------------------|------|-------|-------------------------------------------------|
|``input_type`` | uint | v1 | The input type of the input. See details below |
|``value`` | vec3 | v1 | The value of input |

The `input_type` is defined as a `uint` but is effectively an enumeration. it has the following values:

|Name | Since | Description |
|--------------------------------------------------|-------|---------------------------------------------------------------------------|
|``CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_PREV`` | v1 | The value is provided by the last stage |
|``CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR`` | v1 | The value is provided by the color data |
|``CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_TEX`` | v1 | The value is provided by a texture |
|``CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_RAS`` | v1 | |
|``CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_KONST`` | v1 | The value is a constant value defined by the software |
|``CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_NUMERIC`` | v1 | The value is a constant numeric value like vec3(0, 0, 0) or vec3(1, 1, 1) |

`CustomShaderTevStageInputAlpha` is a single input TEV operation for an alpha value. It has the following structure:

|Name | Type | Since | Description |
|-------------------------|------|-------|-------------------------------------------------------------------------------|
|``input_type`` | uint | v1 | The input type of the input. See `input_type` for color input stages |
|``value`` | uint | v1 | The value of input |


## Examples

Below are a handful of examples.

### Single color

The following shader displays the color red on the screen:

```glsl
vec4 custom_main( in CustomShaderData data )
{
return vec4(1.0, 0.0, 0.0, 1.0);
}
```

### Normal

The following shader displays the normal on the screen:

```glsl
vec4 custom_main( in CustomShaderData data )
{
return vec4(data.normal * 0.5 + 0.5, 1);
}
```

### Reading a texture

The following shader displays the contents of the texture denoted in the shader asset as `MY_TEX`:

```glsl
vec4 custom_main( in CustomShaderData data )
{
return texture(samp[MY_TEX_UNIT], MY_TEX_COORD);
}
```

### Capturing the first texture the game renders with

The following shader would display the contents of the first texture the game uses, ignoring any other operations. If no stages are available or none exist with a texture it would use the final color of all the staging operations:

```glsl
vec4 custom_main( in CustomShaderData data )
{
vec4 final_color = data.final_color;
uint texture_set = 0;
for (uint i = 0; i < data.tev_stage_count; i++)
{
// There are 4 color inputs
for (uint j = 0; j < 4; j++)
{
if (data.tev_stages[i].input_color[j].input_type == CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_TEX && texture_set == 0)
{
final_color = vec4(data.tev_stages[i].input_color[j].value, 1.0);
texture_set = 1;
}
}
}
return final_color;
}
```

### Applying lighting with a point type attenuation

The following shader would apply the lighting for any point lights used during the draw for channel 0's color lights, using blue as a base color:

```glsl
vec4 custom_main( in CustomShaderData data )
{
float total_diffuse = 0;
for (int i = 0; i < data.light_chan0_color_count; i++)
{
if (data.lights_chan0_color[i].attenuation_type == CUSTOM_SHADER_LIGHTING_ATTENUATION_TYPE_POINT)
{
vec3 light_dir = normalize(data.lights_chan0_color[i].position - data.position.xyz);
float attn = (dot(normal, light_dir) >= 0.0) ? max(0.0, dot(normal, data.lights_chan0_color[i].direction.xyz)) : 0.0;
vec3 cosAttn = data.lights_chan0_color[i].cosatt.xyz;
vec3 distAttn = data.lights_chan0_color[i].distatt.xyz;
attn = max(0.0, dot(cosAttn, vec3(1.0, attn, attn*attn))) / dot(distAttn, vec3(1.0, attn, attn * attn));
total_diffuse += attn * max(0.0, dot(normal, light_dir));
}
}
return vec4(total_diffuse * vec3(0, 0, 1), 1);
}
```