mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-09-22 09:18:43 +00:00
Merge branch 'master' of https://github.com/MarioPartyNetplay/Dolphin-MPN
This commit is contained in:
commit
eb30b42683
19 changed files with 792 additions and 680 deletions
|
@ -1,7 +1,7 @@
|
|||
app-id: org.DolphinEmu.dolphin-mpn
|
||||
branch: master
|
||||
runtime: org.kde.Platform
|
||||
runtime-version: 6.4
|
||||
runtime-version: 6.6
|
||||
sdk: org.kde.Sdk
|
||||
command: dolphin-mpn-wrapper
|
||||
rename-desktop-file: dolphin-mpn.desktop
|
||||
|
@ -21,6 +21,8 @@ finish-args:
|
|||
- --allow=bluetooth
|
||||
- --filesystem=xdg-run/app/com.discordapp.Discord:create
|
||||
- --talk-name=org.freedesktop.ScreenSaver
|
||||
# required for Gamescope on Steam Deck
|
||||
- --filesystem=xdg-run/gamescope-0:ro
|
||||
modules:
|
||||
# needed for the bluetooth passthrough feature to work
|
||||
- name: libusb
|
||||
|
|
2
.github/workflows/flatpak.yml
vendored
2
.github/workflows/flatpak.yml
vendored
|
@ -23,7 +23,7 @@ jobs:
|
|||
- uses: actions/checkout@v2
|
||||
# Setup Packages
|
||||
- name: Setup Packages
|
||||
run: sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo && sudo flatpak install org.kde.Sdk//5.15 org.kde.Platform//5.15 -y && sudo dnf install libusb1-devel cmake git gcc-c++ libXext-devel libgudev qt6-qtbase-devel systemd-devel openal-soft-devel libevdev-devel libao-devel SOIL-devel libXrandr-devel pulseaudio-libs-devel bluez-libs-devel p7zip -y
|
||||
run: sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo && sudo flatpak install org.kde.Sdk//5.15 org.kde.Platform//5.15 -y && sudo dnf install libusb1-devel cmake git gcc-c++ libXext-devel libgudev qt6-qtbase-devel systemd-devel openal-soft-devel libevdev-devel libao-devel SOIL-devel libXrandr-devel pulseaudio-libs-devel bluez-libs-devel p7zip SDL2-devel -y
|
||||
# Run Flatpak Builder
|
||||
- name: Run Flatpak Builder
|
||||
uses: flatpak/flatpak-github-actions@v2
|
||||
|
|
45
.github/workflows/macOS.yml
vendored
45
.github/workflows/macOS.yml
vendored
|
@ -1,45 +0,0 @@
|
|||
name: macOS CI
|
||||
|
||||
# Controls when the action will run. Triggers the workflow on push or pull request
|
||||
# events but only for the master branch
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
|
||||
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
|
||||
jobs:
|
||||
# This workflow contains a single job called "build"
|
||||
build:
|
||||
# The type of runner that the job will run on
|
||||
runs-on: macos-latest
|
||||
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
steps:
|
||||
# Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
|
||||
- uses: actions/checkout@v2
|
||||
# Submoudle
|
||||
- name: Checkout Submodles
|
||||
run: git submodule update --init --recursive
|
||||
# Setup Packages
|
||||
- name: Setup Packages
|
||||
run: brew install qt6 p7zip pkgconfig
|
||||
# Setup CMake
|
||||
- name: Setup CMake
|
||||
uses: Symbitic/install-cmake@v0.1.1
|
||||
with:
|
||||
platform: mac
|
||||
# Use Static ZSTD
|
||||
- name: Remove ZSTD
|
||||
run: brew uninstall zstd curl php --force --ignore-dependencies
|
||||
# Run CMake
|
||||
- name: Run CMake
|
||||
run: mkdir -p build && cd build && cmake .. -G Ninja -DCMAKE_CXX_FLAGS="-Xclang -fcolor-diagnostics" -DCMAKE_PREFIX_PATH=$(brew --prefix qt6) -DDISTRIBUTOR="Mario Party Netplay"
|
||||
# Build Project
|
||||
- name: Run Ninja
|
||||
run: cd build && ninja -j8
|
||||
# Upload Artifact
|
||||
- name: Upload Build Artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: DolphinMPN-macOS
|
||||
path: /Users/runner/work/Dolphin-MPN/Dolphin-MPN/build/Binaries/
|
|
@ -199,7 +199,7 @@ C2085CEC 00000005
|
|||
3DC0801D 61CE3F44
|
||||
89CE0000 38000001
|
||||
2C0E000A 41820008
|
||||
48000008 380000XX
|
||||
48000008 38000002
|
||||
39C00000 00000000
|
||||
|
||||
$Mechanics - Double the Amount of Turns [Airsola]
|
||||
|
|
|
@ -369,7 +369,7 @@ C20AFF9C 00000001
|
|||
3880FFD8 00000000
|
||||
*Stars cost 40 instead of the usual 20 coins.
|
||||
|
||||
$Mechanics - Stars Cost 20 Coins from Wiggler [WolfGC64]
|
||||
$Mechanics - Stars Cost 40 Coins from Wiggler [WolfGC64]
|
||||
C20DB528 00000001
|
||||
2C030028 00000000
|
||||
C20DBB40 00000001
|
||||
|
|
|
@ -536,6 +536,18 @@ F6000002 80008180
|
|||
E0000000 80008000
|
||||
*Unlocks Everything in the game.
|
||||
|
||||
$Board - Castaway Bay: Bowser steals 40 Coins instead of 20 [WolfGC64]
|
||||
C2184538 00000001
|
||||
2C140028 00000000
|
||||
C2184544 00000001
|
||||
3880FFD8 00000000
|
||||
|
||||
$Board - Castaway Bay: Ztar only steals Coins [WolfGC64]
|
||||
C21843D8 00000002
|
||||
38600000 2C030001
|
||||
60000000 00000000
|
||||
*When you reach Bowser on Castaway Bay, he'll never take your Stars away, only Coins, like in Mario's Rainbow Castle.
|
||||
|
||||
$Board - Faire Square: Free Whomps [gamemasterplc]
|
||||
F6000001 80008180
|
||||
4080002C 386003A0
|
||||
|
@ -546,7 +558,7 @@ E0000000 80008000
|
|||
|
||||
$Board - Faire Square: Stars are always 20 [Rain]
|
||||
04248064 00000014
|
||||
04248068 0000014
|
||||
04248068 00000014
|
||||
0424806c 00000014
|
||||
04248070 00000014
|
||||
04248074 00000014
|
||||
|
|
|
@ -849,6 +849,14 @@ $Board - Neon Heights: Bowser Path for Free [gamemasterplc]
|
|||
e2000001 80008000
|
||||
*Use Koopa Kid's path for free instead of paying 10 coins in the board Neon Heights.
|
||||
|
||||
$Board - Neon Heights: QOL Junction Addition
|
||||
205B6EB4 C54B8000
|
||||
005b6e71 00000000
|
||||
045b6ea4 00020039
|
||||
025b6ea8 0000001D
|
||||
E2000001 80008000
|
||||
*Replaces the blue space near the Koopa Kid toll with a junction that allows you to choose between going to the usual direction or going to the item shop's zone.
|
||||
|
||||
$Board - Neon Heights: Rocket Minigame is Possible with 1 Player [Airsola]
|
||||
202f2f3c 0000007d
|
||||
28527bfa 00000256
|
||||
|
@ -1083,9 +1091,13 @@ $Mechanics - DK and Bowser Give Only Multiplayer Games [gamemasterplc]
|
|||
*DK and Bowser only give multiplayer minigames which are inclusive and not stupidly broken.
|
||||
|
||||
$Mechanics - Double the Amount of Turns [Rain]
|
||||
C2153644 00000004
|
||||
2C000032 4182000C
|
||||
7C000214 48000008
|
||||
C21522B8 00000005
|
||||
8A440005 7C009000
|
||||
40810014 2C120032
|
||||
4081000C 3A400032
|
||||
9A440005 98040004
|
||||
3A400000 00000000
|
||||
C2153644 00000002
|
||||
38000063 98030005
|
||||
60000000 00000000
|
||||
*50 turns becomes 99 due to integer cap.
|
||||
|
@ -1140,6 +1152,10 @@ $Mechanics - Koopa Kid Orb Is Found in Shops [Ralf]
|
|||
$Mechanics - Hammer Bro Orb Steals 20 Coins [Ralf]
|
||||
041A902C 3AC00014
|
||||
041A9A28 38A0FFEC
|
||||
041A973C 38000000
|
||||
041A9744 3800FFEC
|
||||
041A974C 38000000
|
||||
041A9754 3800FFEC
|
||||
*Hammer Bro steals 20 coins instead of 10 when landing on his space.
|
||||
|
||||
$Mechanics - Happening Star Becomes Star Star [Airsola]
|
||||
|
|
|
@ -1631,10 +1631,8 @@ AchievementManager::ResponseType AchievementManager::Request(
|
|||
const std::string response_str(http_response->begin(), http_response->end());
|
||||
if (process_response(rc_response, response_str.c_str()) != RC_OK)
|
||||
{
|
||||
ERROR_LOG_FMT(
|
||||
ACHIEVEMENTS, "Failed to process HTTP response. \nURL: {} \npost_data: {} \nresponse: {}",
|
||||
api_request.url, api_request.post_data == nullptr ? "NULL" : api_request.post_data,
|
||||
response_str);
|
||||
ERROR_LOG_FMT(ACHIEVEMENTS, "Failed to process HTTP response. \nURL: {} \nresponse: {}",
|
||||
api_request.url, response_str);
|
||||
return ResponseType::MALFORMED_OBJECT;
|
||||
}
|
||||
if (rc_response->response.succeeded)
|
||||
|
@ -1650,9 +1648,7 @@ AchievementManager::ResponseType AchievementManager::Request(
|
|||
}
|
||||
else
|
||||
{
|
||||
WARN_LOG_FMT(ACHIEVEMENTS, "RetroAchievements connection failed. \nURL: {} \npost_data: {}",
|
||||
api_request.url,
|
||||
api_request.post_data == nullptr ? "NULL" : api_request.post_data);
|
||||
WARN_LOG_FMT(ACHIEVEMENTS, "RetroAchievements connection failed. \nURL: {}", api_request.url);
|
||||
return ResponseType::CONNECTION_FAILED;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -692,6 +692,7 @@
|
|||
<ClInclude Include="VideoCommon\GraphicsModSystem\Runtime\Actions\PrintAction.h" />
|
||||
<ClInclude Include="VideoCommon\GraphicsModSystem\Runtime\Actions\ScaleAction.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\FBInfo.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\ScaleAction.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\FBInfo.cpp" />
|
||||
<ClCompile Include="VideoCommon\GraphicsModSystem\Runtime\GraphicsModActionFactory.cpp" />
|
||||
|
|
|
@ -85,6 +85,8 @@ add_library(videocommon
|
|||
GraphicsModSystem/Runtime/Actions/ScaleAction.h
|
||||
GraphicsModSystem/Runtime/Actions/SkipAction.cpp
|
||||
GraphicsModSystem/Runtime/Actions/SkipAction.h
|
||||
GraphicsModSystem/Runtime/CustomPipeline.cpp
|
||||
GraphicsModSystem/Runtime/CustomPipeline.h
|
||||
GraphicsModSystem/Runtime/CustomShaderCache.cpp
|
||||
GraphicsModSystem/Runtime/CustomShaderCache.h
|
||||
GraphicsModSystem/Runtime/FBInfo.cpp
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <optional>
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
|
@ -20,164 +21,12 @@
|
|||
#include "VideoCommon/ShaderGenCommon.h"
|
||||
#include "VideoCommon/TextureCacheBase.h"
|
||||
|
||||
namespace
|
||||
std::unique_ptr<CustomPipelineAction>
|
||||
CustomPipelineAction::Create(std::shared_ptr<VideoCommon::CustomAssetLibrary> library)
|
||||
{
|
||||
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();
|
||||
return std::make_unique<CustomPipelineAction>(std::move(library));
|
||||
}
|
||||
|
||||
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>
|
||||
CustomPipelineAction::Create(const picojson::value& json_data,
|
||||
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));
|
||||
}
|
||||
|
||||
CustomPipelineAction::CustomPipelineAction(std::shared_ptr<VideoCommon::CustomAssetLibrary> library)
|
||||
: m_library(std::move(library))
|
||||
{
|
||||
}
|
||||
|
||||
CustomPipelineAction::CustomPipelineAction(
|
||||
std::shared_ptr<VideoCommon::CustomAssetLibrary> library,
|
||||
std::vector<PipelinePassPassDescription> pass_descriptions)
|
||||
: m_library(std::move(library)), m_passes_config(std::move(pass_descriptions))
|
||||
{
|
||||
m_passes.resize(m_passes_config.size());
|
||||
m_pipeline_passes.resize(m_passes_config.size());
|
||||
}
|
||||
|
||||
CustomPipelineAction::~CustomPipelineAction() = default;
|
||||
|
||||
void CustomPipelineAction::OnDrawStarted(GraphicsModActionData::DrawStarted* draw_started)
|
||||
{
|
||||
if (!draw_started) [[unlikely]]
|
||||
|
@ -254,251 +106,20 @@ void CustomPipelineAction::OnDrawStarted(GraphicsModActionData::DrawStarted* dra
|
|||
if (!draw_started->custom_pixel_shader) [[unlikely]]
|
||||
return;
|
||||
|
||||
if (!draw_started->material_uniform_buffer) [[unlikely]]
|
||||
return;
|
||||
|
||||
if (m_passes.empty()) [[unlikely]]
|
||||
if (m_pipeline_passes.empty()) [[unlikely]]
|
||||
return;
|
||||
|
||||
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];
|
||||
auto& pass = m_pipeline_passes[0];
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
pass.UpdatePixelData(loader, m_library, draw_started->texture_units,
|
||||
pass_config.m_pixel_material_asset);
|
||||
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();
|
||||
custom_pixel_shader.custom_shader = pass.m_last_generated_shader_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->material_uniform_buffer = m_material_data;
|
||||
*draw_started->material_uniform_buffer = pass.m_material_data;
|
||||
}
|
||||
|
|
|
@ -4,20 +4,14 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#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/CustomPipeline.h"
|
||||
#include "VideoCommon/GraphicsModSystem/Runtime/GraphicsModAction.h"
|
||||
#include "VideoCommon/ShaderGenCommon.h"
|
||||
|
||||
class CustomPipelineAction final : public GraphicsModAction
|
||||
{
|
||||
|
@ -30,32 +24,15 @@ public:
|
|||
static std::unique_ptr<CustomPipelineAction>
|
||||
Create(const picojson::value& json_data,
|
||||
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,
|
||||
std::vector<PipelinePassPassDescription> pass_descriptions);
|
||||
~CustomPipelineAction();
|
||||
void OnDrawStarted(GraphicsModActionData::DrawStarted*) 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;
|
||||
|
||||
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;
|
||||
std::vector<CustomPipeline> m_pipeline_passes;
|
||||
};
|
||||
|
|
|
@ -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;
|
||||
};
|
|
@ -839,7 +839,8 @@ void WriteCustomShaderStructImpl(ShaderCode* out, u32 num_stages, bool per_pixel
|
|||
texcoord);
|
||||
}
|
||||
|
||||
GenerateCustomLightingImplementation(out, uid_data->lighting, "colors_");
|
||||
if (per_pixel_lighting)
|
||||
GenerateCustomLightingImplementation(out, uid_data->lighting, "colors_");
|
||||
|
||||
for (u32 i = 0; i < 16; i++)
|
||||
{
|
||||
|
@ -1326,8 +1327,20 @@ ShaderCode GeneratePixelShaderCode(APIType api_type, const ShaderHostConfig& hos
|
|||
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);
|
||||
if (uid_data->uint_output)
|
||||
{
|
||||
out.Write("\t\tcustom_data.final_color = float4(ocol0.x / 255.0, ocol0.y / 255.0, ocol0.z "
|
||||
"/ 255.0, ocol0.w / 255.0);\n");
|
||||
out.Write("\t\tfloat3 custom_output = {}_{}(custom_data).xyz;\n",
|
||||
CUSTOM_PIXELSHADER_COLOR_FUNC, i);
|
||||
out.Write("\t\tocol0.xyz = uint3(custom_output.x * 255, custom_output.y * 255, "
|
||||
"custom_output.z * 255);\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -524,9 +524,9 @@ void PostProcessing::BlitFromTexture(const MathUtil::Rectangle<int>& dst,
|
|||
needs_resampling ? present_rect.GetHeight() : static_cast<u32>(src_rect.GetHeight());
|
||||
|
||||
if (!m_intermediary_frame_buffer || !m_intermediary_color_texture ||
|
||||
m_intermediary_color_texture.get()->GetWidth() != target_width ||
|
||||
m_intermediary_color_texture.get()->GetHeight() != target_height ||
|
||||
m_intermediary_color_texture.get()->GetLayers() != target_layers)
|
||||
m_intermediary_color_texture->GetWidth() != target_width ||
|
||||
m_intermediary_color_texture->GetHeight() != target_height ||
|
||||
m_intermediary_color_texture->GetLayers() != target_layers)
|
||||
{
|
||||
const TextureConfig intermediary_color_texture_config(
|
||||
target_width, target_height, 1, target_layers, src_tex->GetSamples(),
|
||||
|
@ -580,8 +580,8 @@ void PostProcessing::BlitFromTexture(const MathUtil::Rectangle<int>& dst,
|
|||
default_uniform_staging_buffer = false;
|
||||
}
|
||||
|
||||
m_intermediary_frame_buffer.release();
|
||||
m_intermediary_color_texture.release();
|
||||
m_intermediary_frame_buffer.reset();
|
||||
m_intermediary_color_texture.reset();
|
||||
}
|
||||
|
||||
// TODO: ideally we'd do the user selected post process pass in the intermediary buffer in linear
|
||||
|
|
|
@ -127,106 +127,109 @@ void WriteCustomShaderStructImpl(ShaderCode* out, u32 num_texgen, bool per_pixel
|
|||
out->Write("\t}}\n");
|
||||
}
|
||||
|
||||
out->Write("\tuint light_count = 0;\n");
|
||||
out->Write("\tfor (uint chan = 0u; chan < {}u; chan++)\n", NUM_XF_COLOR_CHANNELS);
|
||||
out->Write("\t{{\n");
|
||||
out->Write("\t\tuint colorreg = xfmem_color(chan);\n");
|
||||
out->Write("\t\tuint alphareg = xfmem_alpha(chan);\n");
|
||||
for (const auto& color_type : std::array<std::string_view, 2>{"colorreg", "alphareg"})
|
||||
if (per_pixel_lighting)
|
||||
{
|
||||
if (color_type == "colorreg")
|
||||
out->Write("\tuint light_count = 0;\n");
|
||||
out->Write("\tfor (uint chan = 0u; chan < {}u; chan++)\n", NUM_XF_COLOR_CHANNELS);
|
||||
out->Write("\t{{\n");
|
||||
out->Write("\t\tuint colorreg = xfmem_color(chan);\n");
|
||||
out->Write("\t\tuint alphareg = xfmem_alpha(chan);\n");
|
||||
for (const auto& color_type : std::array<std::string_view, 2>{"colorreg", "alphareg"})
|
||||
{
|
||||
out->Write("\t\tcustom_data.base_material[0] = " I_MATERIALS "[2u] / 255.0; \n");
|
||||
out->Write("\t\tif ({} != 0u)\n", BitfieldExtract<&LitChannel::enablelighting>(color_type));
|
||||
out->Write("\t\t\tcustom_data.base_material[0] = colors_0; \n");
|
||||
}
|
||||
else
|
||||
{
|
||||
out->Write("custom_data.base_material[1].w = " I_MATERIALS "[3u].w / 255.0; \n");
|
||||
out->Write("\t\tif ({} != 0u)\n", BitfieldExtract<&LitChannel::enablelighting>(color_type));
|
||||
out->Write("\t\t\tcustom_data.base_material[1].w = colors_1.w; \n");
|
||||
}
|
||||
out->Write("\t\tif ({} != 0u)\n", BitfieldExtract<&LitChannel::enablelighting>(color_type));
|
||||
out->Write("\t\t{{\n");
|
||||
out->Write("\t\t\tuint light_mask = {} | ({} << 4u);\n",
|
||||
BitfieldExtract<&LitChannel::lightMask0_3>(color_type),
|
||||
BitfieldExtract<&LitChannel::lightMask4_7>(color_type));
|
||||
out->Write("\t\t\tuint attnfunc = {};\n", BitfieldExtract<&LitChannel::attnfunc>(color_type));
|
||||
out->Write("\t\t\tfor (uint light_index = 0u; light_index < 8u; light_index++)\n");
|
||||
out->Write("\t\t\t{{\n");
|
||||
out->Write("\t\t\t\tif ((light_mask & (1u << light_index)) != 0u)\n");
|
||||
out->Write("\t\t\t\t{{\n");
|
||||
// Shader compilation is weird, shader arrays can't use indexing by variable
|
||||
// to set values unless the variable is an index in a for loop.
|
||||
// So instead we have to do this if check nonsense
|
||||
for (u32 light_count_index = 0; light_count_index < 8; light_count_index++)
|
||||
{
|
||||
out->Write("\t\t\t\t\tif (light_index == {})\n", light_count_index);
|
||||
out->Write("\t\t\t\t\t{{\n");
|
||||
if (color_type == "colorreg")
|
||||
{
|
||||
for (u32 channel_index = 0; channel_index < NUM_XF_COLOR_CHANNELS; channel_index++)
|
||||
{
|
||||
out->Write("\t\t\t\t\t\tif (chan == {})\n", channel_index);
|
||||
out->Write("\t\t\t\t\t\t{{\n");
|
||||
out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_color[{}].direction = " I_LIGHTS
|
||||
"[light_index].dir.xyz;\n",
|
||||
channel_index, light_count_index);
|
||||
out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_color[{}].position = " I_LIGHTS
|
||||
"[light_index].pos.xyz;\n",
|
||||
channel_index, light_count_index);
|
||||
out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_color[{}].cosatt = " I_LIGHTS
|
||||
"[light_index].cosatt;\n",
|
||||
channel_index, light_count_index);
|
||||
out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_color[{}].distatt = " I_LIGHTS
|
||||
"[light_index].distatt;\n",
|
||||
channel_index, light_count_index);
|
||||
out->Write(
|
||||
"\t\t\t\t\t\t\tcustom_data.lights_chan{}_color[{}].attenuation_type = attnfunc;\n",
|
||||
channel_index, light_count_index);
|
||||
out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_color[{}].color = " I_LIGHTS
|
||||
"[light_index].color.rgb / float3(255.0, 255.0, 255.0);\n",
|
||||
channel_index, light_count_index);
|
||||
out->Write("\t\t\t\t\t\t\tcustom_data.light_chan{}_color_count += 1;\n", channel_index);
|
||||
out->Write("\t\t\t\t\t\t}}\n");
|
||||
}
|
||||
out->Write("\t\tcustom_data.base_material[0] = " I_MATERIALS "[2u] / 255.0; \n");
|
||||
out->Write("\t\tif ({} != 0u)\n", BitfieldExtract<&LitChannel::enablelighting>(color_type));
|
||||
out->Write("\t\t\tcustom_data.base_material[0] = colors_0; \n");
|
||||
}
|
||||
else
|
||||
{
|
||||
for (u32 channel_index = 0; channel_index < NUM_XF_COLOR_CHANNELS; channel_index++)
|
||||
{
|
||||
out->Write("\t\t\t\t\t\tif (chan == {})\n", channel_index);
|
||||
out->Write("\t\t\t\t\t\t{{\n");
|
||||
out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_alpha[{}].direction = " I_LIGHTS
|
||||
"[light_index].dir.xyz;\n",
|
||||
channel_index, light_count_index);
|
||||
out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_alpha[{}].position = " I_LIGHTS
|
||||
"[light_index].pos.xyz;\n",
|
||||
channel_index, light_count_index);
|
||||
out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_alpha[{}].cosatt = " I_LIGHTS
|
||||
"[light_index].cosatt;\n",
|
||||
channel_index, light_count_index);
|
||||
out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_alpha[{}].distatt = " I_LIGHTS
|
||||
"[light_index].distatt;\n",
|
||||
channel_index, light_count_index);
|
||||
out->Write(
|
||||
"\t\t\t\t\t\t\tcustom_data.lights_chan{}_alpha[{}].attenuation_type = attnfunc;\n",
|
||||
channel_index, light_count_index);
|
||||
out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_alpha[{}].color = float3(" I_LIGHTS
|
||||
"[light_index].color.a) / float3(255.0, 255.0, 255.0);\n",
|
||||
channel_index, light_count_index);
|
||||
out->Write("\t\t\t\t\t\t\tcustom_data.light_chan{}_alpha_count += 1;\n", channel_index);
|
||||
out->Write("\t\t\t\t\t\t}}\n");
|
||||
}
|
||||
out->Write("custom_data.base_material[1].w = " I_MATERIALS "[3u].w / 255.0; \n");
|
||||
out->Write("\t\tif ({} != 0u)\n", BitfieldExtract<&LitChannel::enablelighting>(color_type));
|
||||
out->Write("\t\t\tcustom_data.base_material[1].w = colors_1.w; \n");
|
||||
}
|
||||
out->Write("\t\tif ({} != 0u)\n", BitfieldExtract<&LitChannel::enablelighting>(color_type));
|
||||
out->Write("\t\t{{\n");
|
||||
out->Write("\t\t\tuint light_mask = {} | ({} << 4u);\n",
|
||||
BitfieldExtract<&LitChannel::lightMask0_3>(color_type),
|
||||
BitfieldExtract<&LitChannel::lightMask4_7>(color_type));
|
||||
out->Write("\t\t\tuint attnfunc = {};\n", BitfieldExtract<&LitChannel::attnfunc>(color_type));
|
||||
out->Write("\t\t\tfor (uint light_index = 0u; light_index < 8u; light_index++)\n");
|
||||
out->Write("\t\t\t{{\n");
|
||||
out->Write("\t\t\t\tif ((light_mask & (1u << light_index)) != 0u)\n");
|
||||
out->Write("\t\t\t\t{{\n");
|
||||
// Shader compilation is weird, shader arrays can't use indexing by variable
|
||||
// to set values unless the variable is an index in a for loop.
|
||||
// So instead we have to do this if check nonsense
|
||||
for (u32 light_count_index = 0; light_count_index < 8; light_count_index++)
|
||||
{
|
||||
out->Write("\t\t\t\t\tif (light_index == {})\n", light_count_index);
|
||||
out->Write("\t\t\t\t\t{{\n");
|
||||
if (color_type == "colorreg")
|
||||
{
|
||||
for (u32 channel_index = 0; channel_index < NUM_XF_COLOR_CHANNELS; channel_index++)
|
||||
{
|
||||
out->Write("\t\t\t\t\t\tif (chan == {})\n", channel_index);
|
||||
out->Write("\t\t\t\t\t\t{{\n");
|
||||
out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_color[{}].direction = " I_LIGHTS
|
||||
"[light_index].dir.xyz;\n",
|
||||
channel_index, light_count_index);
|
||||
out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_color[{}].position = " I_LIGHTS
|
||||
"[light_index].pos.xyz;\n",
|
||||
channel_index, light_count_index);
|
||||
out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_color[{}].cosatt = " I_LIGHTS
|
||||
"[light_index].cosatt;\n",
|
||||
channel_index, light_count_index);
|
||||
out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_color[{}].distatt = " I_LIGHTS
|
||||
"[light_index].distatt;\n",
|
||||
channel_index, light_count_index);
|
||||
out->Write(
|
||||
"\t\t\t\t\t\t\tcustom_data.lights_chan{}_color[{}].attenuation_type = attnfunc;\n",
|
||||
channel_index, light_count_index);
|
||||
out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_color[{}].color = " I_LIGHTS
|
||||
"[light_index].color.rgb / float3(255.0, 255.0, 255.0);\n",
|
||||
channel_index, light_count_index);
|
||||
out->Write("\t\t\t\t\t\t\tcustom_data.light_chan{}_color_count += 1;\n", channel_index);
|
||||
out->Write("\t\t\t\t\t\t}}\n");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (u32 channel_index = 0; channel_index < NUM_XF_COLOR_CHANNELS; channel_index++)
|
||||
{
|
||||
out->Write("\t\t\t\t\t\tif (chan == {})\n", channel_index);
|
||||
out->Write("\t\t\t\t\t\t{{\n");
|
||||
out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_alpha[{}].direction = " I_LIGHTS
|
||||
"[light_index].dir.xyz;\n",
|
||||
channel_index, light_count_index);
|
||||
out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_alpha[{}].position = " I_LIGHTS
|
||||
"[light_index].pos.xyz;\n",
|
||||
channel_index, light_count_index);
|
||||
out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_alpha[{}].cosatt = " I_LIGHTS
|
||||
"[light_index].cosatt;\n",
|
||||
channel_index, light_count_index);
|
||||
out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_alpha[{}].distatt = " I_LIGHTS
|
||||
"[light_index].distatt;\n",
|
||||
channel_index, light_count_index);
|
||||
out->Write(
|
||||
"\t\t\t\t\t\t\tcustom_data.lights_chan{}_alpha[{}].attenuation_type = attnfunc;\n",
|
||||
channel_index, light_count_index);
|
||||
out->Write("\t\t\t\t\t\t\tcustom_data.lights_chan{}_alpha[{}].color = float3(" I_LIGHTS
|
||||
"[light_index].color.a) / float3(255.0, 255.0, 255.0);\n",
|
||||
channel_index, light_count_index);
|
||||
out->Write("\t\t\t\t\t\t\tcustom_data.light_chan{}_alpha_count += 1;\n", channel_index);
|
||||
out->Write("\t\t\t\t\t\t}}\n");
|
||||
}
|
||||
}
|
||||
|
||||
out->Write("\t\t\t\t\t}}\n");
|
||||
out->Write("\t\t\t\t\t}}\n");
|
||||
}
|
||||
out->Write("\t\t\t\t}}\n");
|
||||
out->Write("\t\t\t}}\n");
|
||||
out->Write("\t\t}}\n");
|
||||
}
|
||||
out->Write("\t\t\t\t}}\n");
|
||||
out->Write("\t\t\t}}\n");
|
||||
out->Write("\t\t}}\n");
|
||||
out->Write("\t}}\n");
|
||||
}
|
||||
out->Write("\t}}\n");
|
||||
|
||||
for (u32 i = 0; i < 16; i++)
|
||||
{
|
||||
|
@ -1593,8 +1596,20 @@ ShaderCode GenPixelShader(APIType api_type, const ShaderHostConfig& host_config,
|
|||
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);
|
||||
if (uid_data->uint_output)
|
||||
{
|
||||
out.Write("\t\tcustom_data.final_color = float4(ocol0.x / 255.0, ocol0.y / 255.0, ocol0.z "
|
||||
"/ 255.0, ocol0.w / 255.0);\n");
|
||||
out.Write("\t\tfloat3 custom_output = {}_{}(custom_data).xyz;\n",
|
||||
CUSTOM_PIXELSHADER_COLOR_FUNC, i);
|
||||
out.Write("\t\tocol0.xyz = uint3(custom_output.x * 255, custom_output.y * 255, "
|
||||
"custom_output.z * 255);\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -615,8 +615,6 @@ void VertexManagerBase::Flush()
|
|||
}
|
||||
custom_pixel_shader = std::nullopt;
|
||||
}
|
||||
if (skip == true)
|
||||
return;
|
||||
}
|
||||
|
||||
// Now the vertices can be flushed to the GPU. Everything following the CommitBuffer() call
|
||||
|
@ -624,116 +622,47 @@ void VertexManagerBase::Flush()
|
|||
const u32 num_indices = m_index_generator.GetIndexLen();
|
||||
if (num_indices == 0)
|
||||
return;
|
||||
u32 base_vertex, base_index;
|
||||
CommitBuffer(m_index_generator.GetNumVerts(),
|
||||
VertexLoaderManager::GetCurrentVertexFormat()->GetVertexStride(), num_indices,
|
||||
&base_vertex, &base_index);
|
||||
|
||||
if (g_ActiveConfig.backend_info.api_type != APIType::D3D &&
|
||||
g_ActiveConfig.UseVSForLinePointExpand() &&
|
||||
(m_current_primitive_type == PrimitiveType::Points ||
|
||||
m_current_primitive_type == PrimitiveType::Lines))
|
||||
{
|
||||
// VS point/line expansion puts the vertex id at gl_VertexID << 2
|
||||
// That means the base vertex has to be adjusted to match
|
||||
// (The shader adds this after shifting right on D3D, so no need to do this)
|
||||
base_vertex <<= 2;
|
||||
}
|
||||
|
||||
// Texture loading can cause palettes to be applied (-> uniforms -> draws).
|
||||
// Palette application does not use vertices, only a full-screen quad, so this is okay.
|
||||
// Same with GPU texture decoding, which uses compute shaders.
|
||||
g_texture_cache->BindTextures(used_textures);
|
||||
|
||||
// Now we can upload uniforms, as nothing else will override them.
|
||||
geometry_shader_manager.SetConstants(m_current_primitive_type);
|
||||
pixel_shader_manager.SetConstants();
|
||||
if (!custom_pixel_shader_uniforms.empty() &&
|
||||
pixel_shader_manager.custom_constants.data() != custom_pixel_shader_uniforms.data())
|
||||
{
|
||||
pixel_shader_manager.custom_constants_dirty = true;
|
||||
}
|
||||
pixel_shader_manager.custom_constants = custom_pixel_shader_uniforms;
|
||||
UploadUniforms();
|
||||
if (PerfQueryBase::ShouldEmulate())
|
||||
g_perf_query->EnableQuery(bpmem.zcontrol.early_ztest ? PQG_ZCOMP_ZCOMPLOC : PQG_ZCOMP);
|
||||
|
||||
// Update the pipeline, or compile one if needed.
|
||||
UpdatePipelineConfig();
|
||||
UpdatePipelineObject();
|
||||
if (m_current_pipeline_object)
|
||||
if (!skip)
|
||||
{
|
||||
const AbstractPipeline* current_pipeline = m_current_pipeline_object;
|
||||
if (!custom_pixel_shader_contents.shaders.empty())
|
||||
UpdatePipelineConfig();
|
||||
UpdatePipelineObject();
|
||||
if (m_current_pipeline_object)
|
||||
{
|
||||
CustomShaderInstance custom_shaders;
|
||||
custom_shaders.pixel_contents = std::move(custom_pixel_shader_contents);
|
||||
|
||||
switch (g_ActiveConfig.iShaderCompilationMode)
|
||||
const AbstractPipeline* pipeline_object = m_current_pipeline_object;
|
||||
if (!custom_pixel_shader_contents.shaders.empty())
|
||||
{
|
||||
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))
|
||||
if (const auto custom_pipeline =
|
||||
GetCustomPipeline(custom_pixel_shader_contents, m_current_pipeline_config,
|
||||
m_current_uber_pipeline_config, m_current_pipeline_object))
|
||||
{
|
||||
current_pipeline = *pipeline;
|
||||
pipeline_object = custom_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;
|
||||
};
|
||||
RenderDrawCall(pixel_shader_manager, geometry_shader_manager, custom_pixel_shader_contents,
|
||||
custom_pixel_shader_uniforms, m_current_primitive_type, pipeline_object);
|
||||
}
|
||||
g_gfx->SetPipeline(current_pipeline);
|
||||
if (PerfQueryBase::ShouldEmulate())
|
||||
g_perf_query->EnableQuery(bpmem.zcontrol.early_ztest ? PQG_ZCOMP_ZCOMPLOC : PQG_ZCOMP);
|
||||
|
||||
DrawCurrentBatch(base_index, num_indices, base_vertex);
|
||||
INCSTAT(g_stats.this_frame.num_draw_calls);
|
||||
|
||||
if (PerfQueryBase::ShouldEmulate())
|
||||
g_perf_query->DisableQuery(bpmem.zcontrol.early_ztest ? PQG_ZCOMP_ZCOMPLOC : PQG_ZCOMP);
|
||||
|
||||
OnDraw();
|
||||
|
||||
// The EFB cache is now potentially stale.
|
||||
g_framebuffer_manager->FlagPeekCacheAsOutOfDate();
|
||||
}
|
||||
|
||||
// Track the total emulated state draws
|
||||
INCSTAT(g_stats.this_frame.num_draw_calls);
|
||||
|
||||
// Even if we skip the draw, emulated state should still be impacted
|
||||
OnDraw();
|
||||
|
||||
if (PerfQueryBase::ShouldEmulate())
|
||||
g_perf_query->DisableQuery(bpmem.zcontrol.early_ztest ? PQG_ZCOMP_ZCOMPLOC : PQG_ZCOMP);
|
||||
|
||||
// The EFB cache is now potentially stale.
|
||||
g_framebuffer_manager->FlagPeekCacheAsOutOfDate();
|
||||
}
|
||||
|
||||
if (xfmem.numTexGen.numTexGens != bpmem.genMode.numtexgens)
|
||||
|
@ -1122,3 +1051,107 @@ void VertexManagerBase::NotifyCustomShaderCacheOfHostChange(const ShaderHostConf
|
|||
m_custom_shader_cache->SetHostConfig(host_config);
|
||||
m_custom_shader_cache->Reload();
|
||||
}
|
||||
|
||||
void VertexManagerBase::RenderDrawCall(
|
||||
PixelShaderManager& pixel_shader_manager, GeometryShaderManager& geometry_shader_manager,
|
||||
const CustomPixelShaderContents& custom_pixel_shader_contents,
|
||||
std::span<u8> custom_pixel_shader_uniforms, PrimitiveType primitive_type,
|
||||
const AbstractPipeline* current_pipeline)
|
||||
{
|
||||
// Now we can upload uniforms, as nothing else will override them.
|
||||
geometry_shader_manager.SetConstants(primitive_type);
|
||||
pixel_shader_manager.SetConstants();
|
||||
if (!custom_pixel_shader_uniforms.empty() &&
|
||||
pixel_shader_manager.custom_constants.data() != custom_pixel_shader_uniforms.data())
|
||||
{
|
||||
pixel_shader_manager.custom_constants_dirty = true;
|
||||
}
|
||||
pixel_shader_manager.custom_constants = custom_pixel_shader_uniforms;
|
||||
UploadUniforms();
|
||||
|
||||
g_gfx->SetPipeline(current_pipeline);
|
||||
|
||||
u32 base_vertex, base_index;
|
||||
CommitBuffer(m_index_generator.GetNumVerts(),
|
||||
VertexLoaderManager::GetCurrentVertexFormat()->GetVertexStride(),
|
||||
m_index_generator.GetIndexLen(), &base_vertex, &base_index);
|
||||
|
||||
if (g_ActiveConfig.backend_info.api_type != APIType::D3D &&
|
||||
g_ActiveConfig.UseVSForLinePointExpand() &&
|
||||
(primitive_type == PrimitiveType::Points || primitive_type == PrimitiveType::Lines))
|
||||
{
|
||||
// VS point/line expansion puts the vertex id at gl_VertexID << 2
|
||||
// That means the base vertex has to be adjusted to match
|
||||
// (The shader adds this after shifting right on D3D, so no need to do this)
|
||||
base_vertex <<= 2;
|
||||
}
|
||||
|
||||
DrawCurrentBatch(base_index, m_index_generator.GetIndexLen(), base_vertex);
|
||||
}
|
||||
|
||||
const AbstractPipeline* VertexManagerBase::GetCustomPipeline(
|
||||
const CustomPixelShaderContents& custom_pixel_shader_contents,
|
||||
const VideoCommon::GXPipelineUid& current_pipeline_config,
|
||||
const VideoCommon::GXUberPipelineUid& current_uber_pipeline_config,
|
||||
const AbstractPipeline* current_pipeline) const
|
||||
{
|
||||
if (current_pipeline)
|
||||
{
|
||||
if (!custom_pixel_shader_contents.shaders.empty())
|
||||
{
|
||||
CustomShaderInstance custom_shaders;
|
||||
custom_shaders.pixel_contents = custom_pixel_shader_contents;
|
||||
switch (g_ActiveConfig.iShaderCompilationMode)
|
||||
{
|
||||
case ShaderCompilationMode::Synchronous:
|
||||
case ShaderCompilationMode::AsynchronousSkipRendering:
|
||||
{
|
||||
if (auto pipeline = m_custom_shader_cache->GetPipelineAsync(
|
||||
current_pipeline_config, custom_shaders, current_pipeline->m_config))
|
||||
{
|
||||
return *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(
|
||||
current_pipeline_config, custom_shaders, current_pipeline->m_config))
|
||||
{
|
||||
return *pipeline;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (auto pipeline = m_custom_shader_cache->GetPipelineAsync(
|
||||
current_uber_pipeline_config, custom_shaders, current_pipeline->m_config))
|
||||
{
|
||||
return *pipeline;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ShaderCompilationMode::AsynchronousUberShaders:
|
||||
{
|
||||
if (auto pipeline = m_custom_shader_cache->GetPipelineAsync(
|
||||
current_pipeline_config, custom_shaders, current_pipeline->m_config))
|
||||
{
|
||||
return *pipeline;
|
||||
}
|
||||
else if (auto uber_pipeline = m_custom_shader_cache->GetPipelineAsync(
|
||||
current_uber_pipeline_config, custom_shaders, current_pipeline->m_config))
|
||||
{
|
||||
return *uber_pipeline;
|
||||
}
|
||||
}
|
||||
break;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
|
|
@ -15,9 +15,12 @@
|
|||
#include "VideoCommon/ShaderCache.h"
|
||||
#include "VideoCommon/VideoEvents.h"
|
||||
|
||||
struct CustomPixelShaderContents;
|
||||
class CustomShaderCache;
|
||||
class DataReader;
|
||||
class GeometryShaderManager;
|
||||
class NativeVertexFormat;
|
||||
class PixelShaderManager;
|
||||
class PointerWrap;
|
||||
struct PortableVertexDeclaration;
|
||||
|
||||
|
@ -218,9 +221,20 @@ private:
|
|||
// Minimum number of draws per command buffer when attempting to preempt a readback operation.
|
||||
static constexpr u32 MINIMUM_DRAW_CALLS_PER_COMMAND_BUFFER_FOR_READBACK = 10;
|
||||
|
||||
void RenderDrawCall(PixelShaderManager& pixel_shader_manager,
|
||||
GeometryShaderManager& geometry_shader_manager,
|
||||
const CustomPixelShaderContents& custom_pixel_shader_contents,
|
||||
std::span<u8> custom_pixel_shader_uniforms, PrimitiveType primitive_type,
|
||||
const AbstractPipeline* current_pipeline);
|
||||
void UpdatePipelineConfig();
|
||||
void UpdatePipelineObject();
|
||||
|
||||
const AbstractPipeline*
|
||||
GetCustomPipeline(const CustomPixelShaderContents& custom_pixel_shader_contents,
|
||||
const VideoCommon::GXPipelineUid& current_pipeline_config,
|
||||
const VideoCommon::GXUberPipelineUid& current_uber_pipeline_confi,
|
||||
const AbstractPipeline* current_pipeline) const;
|
||||
|
||||
bool m_is_flushed = true;
|
||||
FlushStatistics m_flush_statistics = {};
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue