[GSoC 2017] RetroPlayer: Video Shaders

Includes the following fixes from KOPRajs:

  * Calculate separate POT (Power-Of-Two) texture width and height
  * Update video shader preset if size of the source has changed
  * Fix multipass shaders texture scaling and filtering
  * Use optimal FBO texture sizes
  * Clean up multipass rendering logic
  * Set correct destination rectangle size for the last pass
This commit is contained in:
VelocityRa 2017-08-19 00:44:01 +03:00 committed by Garrett Brown
parent b7126ff52b
commit 801dfd409d
70 changed files with 4157 additions and 236 deletions

1
.gitignore vendored
View File

@ -132,6 +132,7 @@ cmake_install.cmake
/addons/kodi.binary.instance.inputstream/addon.xml
/addons/kodi.binary.instance.peripheral/addon.xml
/addons/kodi.binary.instance.pvr/addon.xml
/addons/kodi.binary.instance.shaderpreset/addon.xml
/addons/kodi.binary.instance.screensaver/addon.xml
/addons/kodi.binary.instance.vfs/addon.xml
/addons/kodi.binary.instance.videocodec/addon.xml

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<addon id="kodi.binary.instance.shaderpreset" version="@ADDON_INSTANCE_VERSION_SHADERPRESET@" provider-name="Team Kodi">
<backwards-compatibility abi="@ADDON_INSTANCE_VERSION_SHADERPRESET_MIN@"/>
<requires>
<import addon="xbmc.core" version="0.1.0"/>
</requires>
</addon>

View File

@ -11,7 +11,7 @@ xbmc/cores/RetroPlayer/playback cores/RetroPlaye
xbmc/cores/RetroPlayer/process cores/RetroPlayer/process
xbmc/cores/RetroPlayer/rendering cores/RetroPlayer/rendering
xbmc/cores/RetroPlayer/rendering/VideoRenderers cores/RetroPlayer/rendering/VideoRenderers
xbmc/cores/RetroPlayer/rendering/VideoShaders cores/RetroPlayer/rendering/VideoShaders
xbmc/cores/RetroPlayer/savestates cores/RetroPlayer/savestates
xbmc/cores/RetroPlayer/shaders cores/RetroPlayer/shaders
xbmc/cores/RetroPlayer/streams cores/RetroPlayer/streams
xbmc/cores/RetroPlayer/streams/memory cores/RetroPlayer/streams/memory

View File

@ -1,5 +1,5 @@
xbmc/cores/RetroPlayer/process/windows cores/RetroPlayer/process/windows
xbmc/cores/RetroPlayer/rendering/VideoShaders/windows cores/RetroPlayer/rendering/VideoShaders/windows
xbmc/cores/RetroPlayer/shaders/windows cores/RetroPlayer/shaders/windows
xbmc/cores/VideoPlayer/Process/windows cores/VideoPlayer/Process/windows
xbmc/cores/VideoPlayer/VideoRenderers/windows cores/VideoPlayer/VideoRenderers/windows
xbmc/input/touch input/touch

View File

@ -40,6 +40,16 @@ IF EXIST BUILD_WIN32\addons\game.libretro.* (
SET /A Counter = !Counter! + 1
)
)
FOR /F "tokens=*" %%P IN ('dir /B /AD BUILD_WIN32\addons\game.shader.*') DO (
FOR /f "delims=<" %%N in ('powershell.exe -ExecutionPolicy Unrestricted -command "& {[xml]$a = get-content BUILD_WIN32\addons\%%P\addon.xml;$a.addon.name}"') do (
ECHO Section "%%N" SecGameAddons!Counter! >> game-addons.nsi
ECHO SectionIn 1 2 >> game-addons.nsi
ECHO SetOutPath "$INSTDIR\addons\%%P" >> game-addons.nsi
ECHO File /r "${app_root}\addons\%%P\*.*" >> game-addons.nsi
ECHO SectionEnd >> game-addons.nsi
SET /A Counter = !Counter! + 1
)
)
ECHO SectionGroupEnd >> game-addons.nsi
)

View File

@ -20,6 +20,7 @@
<addon>kodi.binary.instance.peripheral</addon>
<addon>kodi.binary.instance.pvr</addon>
<addon>kodi.binary.instance.screensaver</addon>
<addon>kodi.binary.instance.shaderpreset</addon>
<addon>kodi.binary.instance.vfs</addon>
<addon>kodi.binary.instance.videocodec</addon>
<addon>kodi.binary.instance.visualization</addon>
@ -52,6 +53,7 @@
<addon>xbmc.metadata</addon>
<addon>xbmc.python</addon>
<addon>xbmc.webinterface</addon>
<addon optional="true">game.shader.presets</addon>
<addon optional="true">inputstream.adaptive</addon>
<addon optional="true">peripheral.joystick</addon>
<addon optional="true">service.xbmc.versioncheck</addon>

View File

@ -200,9 +200,9 @@ bool CServiceManager::InitStageThree(const std::shared_ptr<CProfileManager>& pro
// Peripherals depends on strings being loaded before stage 3
m_peripherals->Initialise();
m_gameServices =
std::make_unique<GAME::CGameServices>(*m_gameControllerManager, *m_gameRenderManager,
*m_peripherals, *profileManager, *m_inputManager);
m_gameServices = std::make_unique<GAME::CGameServices>(
*m_gameControllerManager, *m_gameRenderManager, *m_peripherals, *profileManager,
*m_inputManager, *m_addonMgr);
m_contextMenuManager->Init();

View File

@ -74,6 +74,8 @@ AddonPtr CAddonBuilder::Generate(const AddonInfoPtr& info, AddonType type)
return std::make_shared<CAddonDll>(info, type);
case AddonType::GAMEDLL:
return std::make_shared<GAME::CGameClient>(info);
case AddonType::SHADERDLL:
return std::make_shared<CAddonDll>(info, type);
case AddonType::PLUGIN:
case AddonType::SCRIPT:
return std::make_shared<CPluginSource>(info, type);

View File

@ -18,7 +18,7 @@
namespace ADDON
{
const std::vector<AddonType> ADDONS_TO_CACHE = {AddonType::GAMEDLL};
const std::vector<AddonType> ADDONS_TO_CACHE = {AddonType::GAMEDLL, AddonType::SHADERDLL};
CBinaryAddonCache::~CBinaryAddonCache()
{

View File

@ -25,6 +25,7 @@ set(SOURCES Addon.cpp
Scraper.cpp
ScreenSaver.cpp
Service.cpp
ShaderPreset.cpp
Skin.cpp
UISoundsResource.cpp
VFSEntry.cpp
@ -65,6 +66,7 @@ set(HEADERS Addon.h
Scraper.h
ScreenSaver.h
Service.h
ShaderPreset.h
Skin.h
UISoundsResource.h
VFSEntry.h

View File

@ -0,0 +1,292 @@
/*
* Copyright (C) 2017-2025 Team Kodi
* This file is part of Kodi - https://kodi.tv
*
* SPDX-License-Identifier: GPL-2.0-or-later
* See LICENSES/README.md for more information.
*/
#include "ShaderPreset.h"
#include "addons/addoninfo/AddonInfo.h"
#include "addons/addoninfo/AddonType.h"
#include "addons/binary-addons/BinaryAddonBase.h"
#include "cores/RetroPlayer/shaders/IShaderPreset.h"
#include "filesystem/SpecialProtocol.h"
#include "utils/StringUtils.h"
#include "utils/URIUtils.h"
#include "utils/log.h"
using namespace KODI;
using namespace ADDON;
// --- CShaderPreset -----------------------------------------------------------
CShaderPreset::CShaderPreset(preset_file file, AddonInstance_ShaderPreset& instanceStruct)
: m_file(file), m_struct(instanceStruct)
{
}
CShaderPreset::~CShaderPreset()
{
m_struct.toAddon->PresetFileFree(&m_struct, m_file);
}
bool CShaderPreset::ReadShaderPreset(video_shader& shader)
{
return m_struct.toAddon->VideoShaderRead(&m_struct, m_file, &shader);
}
void CShaderPreset::WriteShaderPreset(const video_shader& shader)
{
return m_struct.toAddon->VideoShaderWrite(&m_struct, m_file, &shader);
}
bool CShaderPreset::ResolveParameters(video_shader& shader)
{
return m_struct.toAddon->VideoShaderResolveParameters(&m_struct, m_file, &shader);
}
void CShaderPreset::FreeShaderPreset(video_shader& shader)
{
m_struct.toAddon->VideoShaderFree(&m_struct, &shader);
}
// --- CShaderPresetAddon ------------------------------------------------------
CShaderPresetAddon::CShaderPresetAddon(const AddonInfoPtr& addonInfo)
: IAddonInstanceHandler(ADDON_INSTANCE_SHADERPRESET, addonInfo)
{
// Create "C" interface structures, used to prevent API problems on update
m_ifc.shaderpreset = new AddonInstance_ShaderPreset;
m_ifc.shaderpreset->props = new AddonProps_ShaderPreset();
m_ifc.shaderpreset->toAddon = new KodiToAddonFuncTable_ShaderPreset();
m_ifc.shaderpreset->toKodi = new AddonToKodiFuncTable_ShaderPreset();
ResetProperties();
// Initialize properties
m_strUserPath = CSpecialProtocol::TranslatePath(Profile());
m_strClientPath = CSpecialProtocol::TranslatePath(Path());
m_extensions = StringUtils::Split(
addonInfo->Type(ADDON::AddonType::SHADERDLL)->GetValue("@extensions").asString(), "|");
}
CShaderPresetAddon::~CShaderPresetAddon(void)
{
DestroyAddon();
}
bool CShaderPresetAddon::CreateAddon(void)
{
std::unique_lock<CSharedSection> lock(m_dllSection);
// Reset all properties to defaults
ResetProperties();
// Initialise the add-on
CLog::Log(LOGDEBUG, "{} - creating ShaderPreset add-on instance '{}'", __FUNCTION__, Name());
if (CreateInstance() != ADDON_STATUS_OK)
return false;
return true;
}
void CShaderPresetAddon::DestroyAddon()
{
std::unique_lock<CSharedSection> lock(m_dllSection);
DestroyInstance();
}
void CShaderPresetAddon::ResetProperties(void)
{
m_ifc.shaderpreset->props->user_path = m_strUserPath.c_str();
m_ifc.shaderpreset->props->addon_path = m_strClientPath.c_str();
m_ifc.shaderpreset->toKodi->kodiInstance = this;
memset(m_ifc.shaderpreset->toAddon, 0, sizeof(KodiToAddonFuncTable_ShaderPreset));
}
bool CShaderPresetAddon::LoadPreset(const std::string& presetPath,
SHADER::IShaderPreset& shaderPreset)
{
bool bSuccess = false;
std::string translatedPath = CSpecialProtocol::TranslatePath(presetPath);
preset_file file =
m_ifc.shaderpreset->toAddon->PresetFileNew(m_ifc.shaderpreset, translatedPath.c_str());
if (file != nullptr)
{
std::unique_ptr<CShaderPreset> shaderPresetAddon =
std::make_unique<CShaderPreset>(file, *m_ifc.shaderpreset);
video_shader videoShader = {};
if (shaderPresetAddon->ReadShaderPreset(videoShader))
{
if (shaderPresetAddon->ResolveParameters(videoShader))
{
TranslateShaderPreset(videoShader, shaderPreset);
bSuccess = true;
}
shaderPresetAddon->FreeShaderPreset(videoShader);
}
}
return bSuccess;
}
//! @todo Instead of copying every parameter to every pass and resolving them
//! later in GetShaderParameters, we should resolve which param goes to which
//! shader in the add-on
void CShaderPresetAddon::TranslateShaderPreset(const video_shader& shader,
SHADER::IShaderPreset& shaderPreset)
{
if (shader.passes != nullptr)
{
for (unsigned int passIdx = 0; passIdx < shader.pass_count; ++passIdx)
{
SHADER::ShaderPass shaderPass;
TranslateShaderPass(shader.passes[passIdx], shaderPass);
if (shader.luts != nullptr)
{
for (unsigned int lutIdx = 0; lutIdx < shader.lut_count; ++lutIdx)
{
SHADER::ShaderLut shaderLut;
TranslateShaderLut(shader.luts[lutIdx], shaderLut);
shaderPass.luts.emplace_back(std::move(shaderLut));
}
}
if (shader.parameters != nullptr)
{
for (unsigned int parIdx = 0; parIdx < shader.parameter_count; ++parIdx)
{
SHADER::ShaderParameter shaderParam;
TranslateShaderParameter(shader.parameters[parIdx], shaderParam);
shaderPass.parameters.emplace_back(std::move(shaderParam));
}
}
shaderPreset.GetPasses().emplace_back(std::move(shaderPass));
}
}
}
void CShaderPresetAddon::TranslateShaderPass(const video_shader_pass& pass,
SHADER::ShaderPass& shaderPass)
{
shaderPass.sourcePath = pass.source_path ? pass.source_path : "";
shaderPass.vertexSource = pass.vertex_source ? pass.vertex_source : "";
shaderPass.fragmentSource = pass.fragment_source ? pass.fragment_source : "";
shaderPass.filterType = TranslateFilterType(pass.filter);
shaderPass.wrapType = TranslateWrapType(pass.wrap);
shaderPass.frameCountMod = pass.frame_count_mod;
const auto& fbo = pass.fbo;
auto& shaderFbo = shaderPass.fbo;
shaderFbo.scaleX.scaleType = TranslateScaleType(fbo.scale_x.type);
switch (fbo.scale_x.type)
{
case SHADER_SCALE_TYPE_ABSOLUTE:
shaderFbo.scaleX.abs = fbo.scale_x.abs;
break;
default:
shaderFbo.scaleX.scale = fbo.scale_x.scale;
break;
}
shaderFbo.scaleY.scaleType = TranslateScaleType(fbo.scale_y.type);
switch (fbo.scale_y.type)
{
case SHADER_SCALE_TYPE_ABSOLUTE:
shaderFbo.scaleY.abs = fbo.scale_y.abs;
break;
default:
shaderFbo.scaleY.scale = fbo.scale_y.scale;
break;
}
shaderFbo.floatFramebuffer = fbo.fp_fbo;
shaderFbo.sRgbFramebuffer = fbo.srgb_fbo;
shaderPass.mipmap = pass.mipmap;
}
void CShaderPresetAddon::TranslateShaderLut(const video_shader_lut& lut,
SHADER::ShaderLut& shaderLut)
{
shaderLut.strId = lut.id ? lut.id : "";
shaderLut.path = lut.path ? lut.path : "";
shaderLut.filterType = TranslateFilterType(lut.filter);
shaderLut.wrapType = TranslateWrapType(lut.wrap);
shaderLut.mipmap = lut.mipmap;
}
void CShaderPresetAddon::TranslateShaderParameter(const video_shader_parameter& param,
SHADER::ShaderParameter& shaderParam)
{
shaderParam.strId = param.id ? param.id : "";
shaderParam.description = param.desc ? param.desc : "";
shaderParam.current = param.current;
shaderParam.minimum = param.minimum;
shaderParam.initial = param.initial;
shaderParam.maximum = param.maximum;
shaderParam.step = param.step;
}
SHADER::FilterType CShaderPresetAddon::TranslateFilterType(SHADER_FILTER_TYPE type)
{
switch (type)
{
case SHADER_FILTER_TYPE_LINEAR:
return SHADER::FilterType::LINEAR;
case SHADER_FILTER_TYPE_NEAREST:
return SHADER::FilterType::NEAREST;
default:
break;
}
return SHADER::FilterType::NONE;
}
SHADER::WrapType CShaderPresetAddon::TranslateWrapType(SHADER_WRAP_TYPE type)
{
switch (type)
{
case SHADER_WRAP_TYPE_BORDER:
return SHADER::WrapType::BORDER;
case SHADER_WRAP_TYPE_EDGE:
return SHADER::WrapType::EDGE;
case SHADER_WRAP_TYPE_REPEAT:
return SHADER::WrapType::REPEAT;
case SHADER_WRAP_TYPE_MIRRORED_REPEAT:
return SHADER::WrapType::MIRRORED_REPEAT;
default:
break;
}
return SHADER::WrapType::BORDER;
}
SHADER::ScaleType CShaderPresetAddon::TranslateScaleType(SHADER_SCALE_TYPE type)
{
switch (type)
{
case SHADER_SCALE_TYPE_INPUT:
return SHADER::ScaleType::INPUT;
case SHADER_SCALE_TYPE_ABSOLUTE:
return SHADER::ScaleType::ABSOLUTE_SCALE;
case SHADER_SCALE_TYPE_VIEWPORT:
return SHADER::ScaleType::VIEWPORT;
default:
break;
}
return SHADER::ScaleType::INPUT;
}

122
xbmc/addons/ShaderPreset.h Normal file
View File

@ -0,0 +1,122 @@
/*
* Copyright (C) 2017-2025 Team Kodi
* This file is part of Kodi - https://kodi.tv
*
* SPDX-License-Identifier: GPL-2.0-or-later
* See LICENSES/README.md for more information.
*/
#pragma once
#include "addons/binary-addons/AddonInstanceHandler.h"
#include "addons/kodi-dev-kit/include/kodi/addon-instance/ShaderPreset.h"
#include "cores/RetroPlayer/shaders/IShaderPresetLoader.h"
#include "cores/RetroPlayer/shaders/ShaderTypes.h"
#include "threads/SharedSection.h"
#include <string>
#include <vector>
namespace ADDON
{
class CAddonInfo;
typedef std::shared_ptr<CAddonInfo> AddonInfoPtr;
class CShaderPreset
{
public:
CShaderPreset(preset_file file, AddonInstance_ShaderPreset& instanceStruct);
~CShaderPreset();
/*!
* \brief Loads preset file and all associated state (passes, textures,
* imports, etc)
*
* \param shader Shader passes handle
*
* \return True if successful, otherwise false
*/
bool ReadShaderPreset(video_shader& shader);
/*!
* \brief Save preset and all associated state (passes, textures, imports,
* etc) to disk
*
* \param shader Shader passes handle
*/
void WriteShaderPreset(const video_shader& shader);
/*!
* \brief Resolve all shader parameters belonging to the shader preset
*
* \param shader Shader passes handle
*
* \return True if successful, otherwise false
*/
bool ResolveParameters(video_shader& shader);
/*!
* \brief Frees a preset file and all associated resources
*
* \param shader Shader passes handle
*/
void FreeShaderPreset(video_shader& shader);
private:
preset_file m_file;
AddonInstance_ShaderPreset& m_struct;
};
/*!
* \brief Wrapper class that wraps the shader presets add-on
*/
class CShaderPresetAddon : public IAddonInstanceHandler, public KODI::SHADER::IShaderPresetLoader
{
public:
CShaderPresetAddon(const AddonInfoPtr& addonInfo);
~CShaderPresetAddon() override;
/*!
* \brief Initialise the instance of this add-on
*/
bool CreateAddon();
/*!
* \brief Deinitialize the instance of this add-on
*/
void DestroyAddon();
/*!
* \brief Get the shader preset extensions supported by this add-on
*/
const std::vector<std::string>& GetExtensions() const { return m_extensions; }
// Implementation of IShaderPresetLoader
bool LoadPreset(const std::string& presetPath,
KODI::SHADER::IShaderPreset& shaderPreset) override;
private:
/*!
* \brief Reset all class members to their defaults. Called by the constructors
*/
void ResetProperties(void);
static void TranslateShaderPreset(const video_shader& shader,
KODI::SHADER::IShaderPreset& shaderPreset);
static void TranslateShaderPass(const video_shader_pass& pass,
KODI::SHADER::ShaderPass& shaderPass);
static void TranslateShaderLut(const video_shader_lut& lut, KODI::SHADER::ShaderLut& shaderLut);
static void TranslateShaderParameter(const video_shader_parameter& param,
KODI::SHADER::ShaderParameter& shaderParam);
static KODI::SHADER::FilterType TranslateFilterType(SHADER_FILTER_TYPE type);
static KODI::SHADER::WrapType TranslateWrapType(SHADER_WRAP_TYPE type);
static KODI::SHADER::ScaleType TranslateScaleType(SHADER_SCALE_TYPE type);
// Cache for const char* members in AddonProps_ShaderPreset
std::string m_strUserPath; /*!< \brief Translated path to the user profile */
std::string m_strClientPath; /*!< \brief Translated path to this add-on */
std::vector<std::string> m_extensions;
CSharedSection m_dllSection;
};
} // namespace ADDON

View File

@ -38,7 +38,7 @@ typedef struct
} TypeMapping;
// clang-format off
static constexpr const std::array<TypeMapping, 40> types =
static constexpr const std::array<TypeMapping, 41> types =
{{
{"unknown", "", AddonType::UNKNOWN, 0, AddonInstanceSupport::SUPPORT_NONE, "" },
{"xbmc.metadata.scraper.albums", "", AddonType::SCRAPER_ALBUMS, 24016, AddonInstanceSupport::SUPPORT_NONE, "DefaultAddonAlbumInfo.png" },
@ -63,6 +63,7 @@ static constexpr const std::array<TypeMapping, 40> types =
{"xbmc.addon.repository", "", AddonType::REPOSITORY, 24011, AddonInstanceSupport::SUPPORT_NONE, "DefaultAddonRepository.png" },
{"kodi.pvrclient", "xbmc.pvrclient", AddonType::PVRDLL, 24019, AddonInstanceSupport::SUPPORT_SETTINGS, "DefaultAddonPVRClient.png" },
{"kodi.gameclient", "", AddonType::GAMEDLL, 35049, AddonInstanceSupport::SUPPORT_OPTIONAL, "DefaultAddonGame.png" },
{"kodi.shader.presets", "", AddonType::SHADERDLL, 35256, AddonInstanceSupport::SUPPORT_NONE, "DefaultAddonGame.png" },
{"kodi.peripheral", "", AddonType::PERIPHERALDLL, 35010, AddonInstanceSupport::SUPPORT_MANDATORY, "DefaultAddonPeripheral.png" },
{"xbmc.addon.video", "", AddonType::VIDEO, 1037, AddonInstanceSupport::SUPPORT_NONE, "DefaultAddonVideo.png" },
{"xbmc.addon.audio", "", AddonType::AUDIO, 1038, AddonInstanceSupport::SUPPORT_NONE, "DefaultAddonMusic.png" },

View File

@ -24,6 +24,7 @@ enum class AddonType
PVRDLL,
INPUTSTREAM,
GAMEDLL,
SHADERDLL,
PERIPHERALDLL,
SCRIPT,
SCRIPT_WEATHER,

View File

@ -10,6 +10,7 @@ set(HEADERS
PVR.h
Peripheral.h
Screensaver.h
ShaderPreset.h
VFS.h
VideoCodec.h
Visualization.h

View File

@ -0,0 +1,268 @@
/*
* Copyright (C) 2017-2025 Team Kodi
* This file is part of Kodi - https://kodi.tv
*
* SPDX-License-Identifier: GPL-2.0-or-later
* See LICENSES/README.md for more information.
*/
#pragma once
#include "../AddonBase.h"
#include "../c-api/addon-instance/shaderpreset.h"
#include <stdint.h>
#ifdef __cplusplus
namespace kodi
{
namespace addon
{
//==============================================================================
/// @addtogroup cpp_kodi_addon_shader_preset
/// @brief @cpp_class{ kodi::addon::CInstanceShaderPreset }
/// **Shader preset add-on instance**\n
/// This class provides the basic shader preset processing system for use as
/// an add-on in Kodi.
///
/// This class is created at addon by Kodi.
///
class ATTR_DLL_LOCAL CInstanceShaderPreset : public IAddonInstance
{
public:
//============================================================================
/// @defgroup cpp_kodi_addon_game_Base 1. Basic functions
/// @ingroup cpp_kodi_addon_game
/// @brief **Functions to manage the addon and get basic information about it**
///
///@{
//============================================================================
/// @brief Shader preset class constructor
///
/// Used by an add-on that only supports Shader Presets, and only in one
/// instance.
///
/// This class is created in the addon by Kodi.
///
///
/// --------------------------------------------------------------------------
///
///
/// **Here's an example of how to use this class:**
/// ~~~~~~~~~~~~~{.cpp}
/// #include <kodi/addon-instance/ShaderPreset.h>
/// ...
///
/// class ATTR_DLL_LOCAL CShaderPresetExample
/// : public kodi::addon::CAddonBase,
/// public kodi::addon::CInstanceShaderPreset
/// {
/// public:
/// CShaderPresetExample()
/// {
/// }
///
/// virtual ~CShaderPresetExample();
/// {
/// }
///
/// ...
/// };
///
/// ADDONCREATOR(CShaderPresetExample)
/// ~~~~~~~~~~~~~
///
CInstanceShaderPreset()
: IAddonInstance(IInstanceInfo(CPrivateBase::m_interface->firstKodiInstance))
{
if (CPrivateBase::m_interface->globalSingleInstance != nullptr)
throw std::logic_error("kodi::addon::CInstanceShaderPreset: Creation of "
"more than one in single instance is not allowed!");
SetAddonStruct(CPrivateBase::m_interface->firstKodiInstance);
CPrivateBase::m_interface->globalSingleInstance = this;
}
//----------------------------------------------------------------------------
//============================================================================
/// @brief Destructor
///
~CInstanceShaderPreset() override = default;
//----------------------------------------------------------------------------
//============================================================================
/// @brief Used to get the full path to the add-on's user profile
///
/// @return Path to the user profile
///
/// @remarks Only called from the add-on
///
std::string UserPath() const
{
if (m_instanceData->props->user_path != nullptr)
return m_instanceData->props->user_path;
return "";
}
//----------------------------------------------------------------------------
//============================================================================
/// @brief Used to get the full path where the add-on is installed
///
/// @return The add-on installation path
///
/// @remarks Only called from the add-on itself
///
std::string AddonPath() const
{
if (m_instanceData->props->addon_path != nullptr)
return m_instanceData->props->addon_path;
return "";
}
//----------------------------------------------------------------------------
//============================================================================
/// @brief **Loads a preset file**
///
/// @param path The path to the preset file
///
/// @return The preset file, or NULL if file doesn't exist
///
virtual preset_file PresetFileNew(const char* path) { return nullptr; }
//----------------------------------------------------------------------------
//============================================================================
/// @brief **Free a preset file**
///
virtual void PresetFileFree(preset_file file) {}
//----------------------------------------------------------------------------
//============================================================================
/// @brief Loads preset file and all associated state (passes, textures,
/// imports, etc)
///
/// @param file Preset file to read from
/// @param shader Shader passes handle
///
/// @return True if successful, otherwise false
///
virtual bool ShaderPresetRead(preset_file file, video_shader& shader) { return false; }
//----------------------------------------------------------------------------
//============================================================================
/// @brief Save preset and all associated state (passes, textures, imports,
/// etc) to disk
///
/// @param file Preset file to read from
/// @param shader Shader passes handle
///
virtual void ShaderPresetWrite(preset_file file, const video_shader& shader) {}
//----------------------------------------------------------------------------
//============================================================================
/// @brief Resolve all shader parameters belonging to the shader preset
///
/// @param file Preset file to read from
/// @param shader Shader passes handle
///
/// @return True if successful, otherwise false
///
virtual bool ShaderPresetResolveParameters(preset_file file, video_shader& shader)
{
return false;
}
//----------------------------------------------------------------------------
//============================================================================
/// @brief Free all state related to shader preset
///
/// @param The shader object to free
///
virtual void ShaderPresetFree(video_shader& shader) {}
//----------------------------------------------------------------------------
///@}
private:
void SetAddonStruct(KODI_ADDON_INSTANCE_STRUCT* instance)
{
instance->hdl = this;
instance->shaderpreset->toAddon->PresetFileNew = ADDON_preset_file_new;
instance->shaderpreset->toAddon->PresetFileFree = ADDON_preset_file_free;
instance->shaderpreset->toAddon->VideoShaderRead = ADDON_video_shader_read_file;
instance->shaderpreset->toAddon->VideoShaderWrite = ADDON_video_shader_write_file;
instance->shaderpreset->toAddon->VideoShaderResolveParameters =
ADDON_video_shader_resolve_parameters;
instance->shaderpreset->toAddon->VideoShaderFree = ADDON_video_shader_free;
m_instanceData = instance->shaderpreset;
m_instanceData->toAddon->addonInstance = this;
}
// --- Shader preset operations ----------------------------------------------
inline static preset_file ADDON_preset_file_new(const AddonInstance_ShaderPreset* addonInstance,
const char* path)
{
return static_cast<CInstanceShaderPreset*>(
static_cast<CInstanceShaderPreset*>(addonInstance->toAddon->addonInstance))
->PresetFileNew(path);
}
inline static void ADDON_preset_file_free(const AddonInstance_ShaderPreset* addonInstance,
preset_file file)
{
return static_cast<CInstanceShaderPreset*>(addonInstance->toAddon->addonInstance)
->PresetFileFree(file);
}
inline static bool ADDON_video_shader_read_file(const AddonInstance_ShaderPreset* addonInstance,
preset_file file,
video_shader* shader)
{
if (shader != nullptr)
return static_cast<CInstanceShaderPreset*>(addonInstance->toAddon->addonInstance)
->ShaderPresetRead(file, *shader);
return false;
}
inline static void ADDON_video_shader_write_file(const AddonInstance_ShaderPreset* addonInstance,
preset_file file,
const video_shader* shader)
{
if (shader != nullptr)
static_cast<CInstanceShaderPreset*>(addonInstance->toAddon->addonInstance)
->ShaderPresetWrite(file, *shader);
}
inline static bool ADDON_video_shader_resolve_parameters(
const AddonInstance_ShaderPreset* addonInstance, preset_file file, video_shader* shader)
{
if (shader != nullptr)
return static_cast<CInstanceShaderPreset*>(addonInstance->toAddon->addonInstance)
->ShaderPresetResolveParameters(file, *shader);
return false;
}
inline static void ADDON_video_shader_free(const AddonInstance_ShaderPreset* addonInstance,
video_shader* shader)
{
if (shader != nullptr)
static_cast<CInstanceShaderPreset*>(addonInstance->toAddon->addonInstance)
->ShaderPresetFree(*shader);
}
AddonInstance_ShaderPreset* m_instanceData;
};
} /* namespace addon */
} /* namespace kodi */
#endif /* __cplusplus */

View File

@ -10,6 +10,7 @@ set(HEADERS
peripheral.h
pvr.h
screensaver.h
shaderpreset.h
vfs.h
video_codec.h
visualization.h

View File

@ -0,0 +1,303 @@
/*
* Copyright (C) 2022-2025 Team Kodi
* This file is part of Kodi - https://kodi.tv
*
* SPDX-License-Identifier: GPL-2.0-or-later
* See LICENSES/README.md for more information.
*/
#ifndef C_API_ADDONINSTANCE_SHADER_PRESET_H
#define C_API_ADDONINSTANCE_SHADER_PRESET_H
#include "../addon_base.h"
#include <stdbool.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C"
{
#endif /* __cplusplus */
//============================================================================
/// @ingroup cpp_kodi_addon_shader_preset_Defs
/// @brief **The data system used for shader presets**
///
///@{
typedef void* preset_file;
/*!
* \brief Scale types
*
* If no scale type is specified, it is assumed that the scale type is
* relative to the input with a scaling factor of 1.0.
*
* Exceptions: If no scale type is set for the last pass, it is assumed to
* output at the full resolution rather than assuming of scale of 1.0, and
* bypasses any frame-buffer object rendering.
*/
typedef enum SHADER_SCALE_TYPE
{
/*!
* \brief Use the source size
*
* Output size of the shader pass is relative to the input size. Value is
* float.
*/
SHADER_SCALE_TYPE_INPUT,
/*!
* \brief Use the window viewport size
*
* Output size of the shader pass is relative to the size of the window
* viewport. Value is float. This value can change over time if the user
* resizes his/her window!
*/
SHADER_SCALE_TYPE_ABSOLUTE,
/*!
* \brief Use a statically defined size
*
* Output size is statically defined to a certain size. Useful for hi-res
* blenders or similar.
*/
SHADER_SCALE_TYPE_VIEWPORT
} SHADER_SCALE_TYPE;
typedef enum SHADER_FILTER_TYPE
{
SHADER_FILTER_TYPE_UNSPEC,
SHADER_FILTER_TYPE_LINEAR,
SHADER_FILTER_TYPE_NEAREST
} SHADER_FILTER_TYPE;
/*!
* \brief Texture wrapping mode
*/
typedef enum SHADER_WRAP_TYPE
{
SHADER_WRAP_TYPE_BORDER, /* Deprecated, will be translated to EDGE in GLES */
SHADER_WRAP_TYPE_EDGE,
SHADER_WRAP_TYPE_REPEAT,
SHADER_WRAP_TYPE_MIRRORED_REPEAT
} SHADER_WRAP_TYPE;
/*!
* \brief FBO scaling parameters for a single axis
*/
typedef struct fbo_scale_axis
{
SHADER_SCALE_TYPE type;
union
{
float scale;
unsigned abs;
};
} fbo_scale_axis;
/*!
* \brief FBO parameters
*/
typedef struct fbo_scale
{
/*!
* \brief sRGB framebuffer
*/
bool srgb_fbo;
/*!
* \brief Float framebuffer
*
* This parameter defines if the pass should be rendered to a 32-bit
* floating point buffer. This only takes effect if the pass is actually
* rendered to an FBO. This is useful for shaders which have to store FBO
* values outside the range [0, 1].
*/
bool fp_fbo;
/*!
* \brief Scaling parameters for X axis
*/
fbo_scale_axis scale_x;
/*!
* \brief Scaling parameters for Y axis
*/
fbo_scale_axis scale_y;
} fbo_scale;
typedef struct video_shader_parameter
{
char* id;
char* desc;
float current;
float minimum;
float initial;
float maximum;
float step;
} video_shader_parameter;
typedef struct video_shader_pass
{
/*!
* \brief Path to the shader pass source
*/
char* source_path;
/*!
* \brief The vertex shader source
*/
char* vertex_source;
/*!
* \brief The fragment shader source, if separate from the vertex source, or
* NULL otherwise
*/
char* fragment_source;
/*!
* \brief FBO parameters
*/
fbo_scale fbo;
/*!
* \brief Defines how the result of this pass will be filtered
*
* @todo Define behavior for unspecified filter
*/
SHADER_FILTER_TYPE filter;
/*!
* \brief Wrapping mode
*/
SHADER_WRAP_TYPE wrap;
/*!
* \brief Frame count mod
*/
unsigned frame_count_mod;
/*!
* \brief Mipmapping
*/
bool mipmap;
} video_shader_pass;
typedef struct video_shader_lut
{
/*!
* \brief Name of the sampler uniform, e.g. `uniform sampler2D foo`.
*/
char* id;
/*!
* \brief Path of the texture
*/
char* path;
/*!
* \brief Filtering for the texture
*/
SHADER_FILTER_TYPE filter;
/*!
* \brief Texture wrapping mode
*/
SHADER_WRAP_TYPE wrap;
/*!
* \brief Use mipmapping for the texture
*/
bool mipmap;
} video_shader_lut;
typedef struct video_shader
{
unsigned pass_count;
video_shader_pass* passes;
unsigned lut_count;
video_shader_lut* luts;
unsigned parameter_count;
video_shader_parameter* parameters;
} video_shader;
//----------------------------------------------------------------------------
///@}
//--==----==----==----==----==----==----==----==----==----==----==----==----==--
/*!
* @brief ShaderPreset properties
*
* Not to be used outside this header.
*/
typedef struct AddonProps_ShaderPreset
{
/*!
* \brief The path to the user profile
*/
const char* user_path;
/*!
* \brief The path to this add-on
*/
const char* addon_path;
} AddonProps_ShaderPreset;
/*!
* \brief Structure to transfer the methods from kodi_shader_preset_dll.h to
* Kodi
*/
struct AddonInstance_ShaderPreset;
/*!
* @brief ShaderPreset callbacks
*
* Not to be used outside this header.
*/
typedef struct AddonToKodiFuncTable_ShaderPreset
{
KODI_HANDLE kodiInstance;
} AddonToKodiFuncTable_ShaderPreset;
/*!
* @brief ShaderPreset function hooks
*
* Not to be used outside this header.
*/
typedef struct KodiToAddonFuncTable_ShaderPreset
{
KODI_HANDLE addonInstance;
preset_file(__cdecl* PresetFileNew)(const struct AddonInstance_ShaderPreset*, const char*);
void(__cdecl* PresetFileFree)(const struct AddonInstance_ShaderPreset*, preset_file);
bool(__cdecl* VideoShaderRead)(const struct AddonInstance_ShaderPreset*,
preset_file,
struct video_shader*);
void(__cdecl* VideoShaderWrite)(const struct AddonInstance_ShaderPreset*,
preset_file,
const struct video_shader*);
bool(__cdecl* VideoShaderResolveParameters)(const struct AddonInstance_ShaderPreset*,
preset_file,
struct video_shader*);
void(__cdecl* VideoShaderFree)(const struct AddonInstance_ShaderPreset*, struct video_shader*);
} KodiToAddonFuncTable_ShaderPreset;
/*!
* @brief ShaderPreset instance
*
* Not to be used outside this header.
*/
typedef struct AddonInstance_ShaderPreset
{
struct AddonProps_ShaderPreset* props;
struct AddonToKodiFuncTable_ShaderPreset* toKodi;
struct KodiToAddonFuncTable_ShaderPreset* toAddon;
} AddonInstance_ShaderPreset;
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* !C_API_ADDONINSTANCE_SHADER_PRESET_H */

View File

@ -281,6 +281,7 @@ extern "C"
struct AddonInstance_Peripheral* peripheral;
struct AddonInstance_PVR* pvr;
struct AddonInstance_Screensaver* screensaver;
struct AddonInstance_ShaderPreset* shaderpreset;
struct AddonInstance_VFSEntry* vfs;
struct AddonInstance_VideoCodec* videocodec;
struct AddonInstance_Visualization* visualization;

View File

@ -163,6 +163,11 @@
#define ADDON_INSTANCE_VERSION_SCREENSAVER_DEPENDS "c-api/addon-instance/screensaver.h" \
"addon-instance/Screensaver.h"
#define ADDON_INSTANCE_VERSION_SHADERPRESET "1.0.0"
#define ADDON_INSTANCE_VERSION_SHADERPRESET_MIN "1.0.0"
#define ADDON_INSTANCE_VERSION_SHADERPRESET_XML_ID "kodi.binary.instance.shaderpreset"
#define ADDON_INSTANCE_VERSION_SHADERPRESET_DEPENDS "addon-instance/ShaderPreset.h"
#define ADDON_INSTANCE_VERSION_VFS "3.0.1"
#define ADDON_INSTANCE_VERSION_VFS_MIN "3.0.1"
#define ADDON_INSTANCE_VERSION_VFS_XML_ID "kodi.binary.instance.vfs"
@ -247,6 +252,9 @@ typedef enum ADDON_TYPE
/// Video codec instance, see @ref cpp_kodi_addon_videocodec "kodi::addon::CInstanceVideoCodec"
ADDON_INSTANCE_VIDEOCODEC = 112,
/// Shader preset instance, see @ref cpp_kodi_addon_shaderpreset "kodi::addon::CInstanceShaderPreset"
ADDON_INSTANCE_SHADERPRESET = 113,
} ADDON_TYPE;
///@}
//------------------------------------------------------------------------------
@ -333,6 +341,10 @@ inline const char* GetTypeVersion(int type)
case ADDON_INSTANCE_SCREENSAVER:
return ADDON_INSTANCE_VERSION_SCREENSAVER;
#endif
#if !defined(BUILD_KODI_ADDON) || defined(ADDON_INSTANCE_VERSION_SHADERPRESET_USED)
case ADDON_INSTANCE_SHADERPRESET:
return ADDON_INSTANCE_VERSION_SHADERPRESET;
#endif
#if !defined(BUILD_KODI_ADDON) || defined(ADDON_INSTANCE_VERSION_VFS_USED)
case ADDON_INSTANCE_VFS:
return ADDON_INSTANCE_VERSION_VFS;
@ -394,6 +406,8 @@ inline const char* GetTypeMinVersion(int type)
return ADDON_INSTANCE_VERSION_PVR_MIN;
case ADDON_INSTANCE_SCREENSAVER:
return ADDON_INSTANCE_VERSION_SCREENSAVER_MIN;
case ADDON_INSTANCE_SHADERPRESET:
return ADDON_INSTANCE_VERSION_SHADERPRESET_MIN;
case ADDON_INSTANCE_VFS:
return ADDON_INSTANCE_VERSION_VFS_MIN;
case ADDON_INSTANCE_VISUALIZATION:
@ -448,6 +462,8 @@ inline const char* GetTypeName(int type)
return "PVR";
case ADDON_INSTANCE_SCREENSAVER:
return "ScreenSaver";
case ADDON_INSTANCE_SHADERPRESET:
return "ShaderPreset";
case ADDON_INSTANCE_VISUALIZATION:
return "Visualization";
case ADDON_INSTANCE_VIDEOCODEC:
@ -499,6 +515,8 @@ inline int GetTypeId(const char* name)
return ADDON_INSTANCE_PVR;
else if (strcmp(name, "screensaver") == 0)
return ADDON_INSTANCE_SCREENSAVER;
else if (strcmp(name, "shaderpreset") == 0)
return ADDON_INSTANCE_SHADERPRESET;
else if (strcmp(name, "vfs") == 0)
return ADDON_INSTANCE_VFS;
else if (strcmp(name, "visualization") == 0)

View File

@ -556,6 +556,7 @@ std::shared_ptr<CRPBaseRenderer> CRPRenderManager::GetRendererForSettings(
renderer->SetScalingMethod(effectiveRenderSettings.VideoSettings().GetScalingMethod());
renderer->SetStretchMode(effectiveRenderSettings.VideoSettings().GetRenderStretchMode());
renderer->SetRenderRotation(effectiveRenderSettings.VideoSettings().GetRenderRotation());
renderer->SetShaderPreset(effectiveRenderSettings.VideoSettings().GetShaderPreset());
renderer->SetPixels(effectiveRenderSettings.VideoSettings().GetPixels());
}
@ -589,10 +590,16 @@ std::shared_ptr<CRPBaseRenderer> CRPRenderManager::GetRendererForPool(
// If buffer pool has no compatible renderers, create one now
if (!renderer)
{
const std::string& shaderPreset = renderSettings.VideoSettings().GetShaderPreset();
CLog::Log(LOGDEBUG, "RetroPlayer[RENDER]: Creating renderer for {}",
m_processInfo.GetRenderSystemName(bufferPool));
renderer.reset(m_processInfo.CreateRenderer(bufferPool, renderSettings));
// Try to create a renderer now, unless the shader preset has failed already
if (shaderPreset.empty() ||
m_failedShaderPresets.find(shaderPreset) == m_failedShaderPresets.end())
renderer.reset(m_processInfo.CreateRenderer(bufferPool, renderSettings));
if (renderer && renderer->Configure(m_format))
{
// Ensure we have a render buffer for this renderer
@ -602,6 +609,10 @@ std::shared_ptr<CRPBaseRenderer> CRPRenderManager::GetRendererForPool(
}
else
renderer.reset();
// If we failed to create a renderer, blocklist the shader preset
if (!renderer && !shaderPreset.empty())
m_failedShaderPresets.insert(shaderPreset);
}
return renderer;

View File

@ -8,28 +8,39 @@
#include "RenderVideoSettings.h"
#include "utils/log.h"
using namespace KODI;
using namespace RETRO;
#define VIDEO_FILTER_NEAREST "nearest"
#define VIDEO_FILTER_LINEAR "linear"
#define VIDEO_FILTER_DEFAULT VIDEO_FILTER_NEAREST
void CRenderVideoSettings::Reset()
{
m_scalingMethod = SCALINGMETHOD::AUTO;
m_stretchMode = STRETCHMODE::Normal;
m_rotationDegCCW = 0;
m_shaderPreset.clear();
m_pixelPath.clear();
}
bool CRenderVideoSettings::operator==(const CRenderVideoSettings& rhs) const
{
return m_scalingMethod == rhs.m_scalingMethod && m_stretchMode == rhs.m_stretchMode &&
m_rotationDegCCW == rhs.m_rotationDegCCW && m_pixelPath == rhs.m_pixelPath;
m_rotationDegCCW == rhs.m_rotationDegCCW && m_shaderPreset == rhs.m_shaderPreset &&
m_pixelPath == rhs.m_pixelPath;
}
bool CRenderVideoSettings::operator<(const CRenderVideoSettings& rhs) const
{
if (m_shaderPreset < rhs.m_shaderPreset)
return true;
if (m_shaderPreset > rhs.m_shaderPreset)
return false;
if (m_scalingMethod < rhs.m_scalingMethod)
return true;
if (m_scalingMethod > rhs.m_scalingMethod)
@ -55,6 +66,13 @@ bool CRenderVideoSettings::operator<(const CRenderVideoSettings& rhs) const
std::string CRenderVideoSettings::GetVideoFilter() const
{
// Sanity check
if (!m_shaderPreset.empty() && m_scalingMethod != SCALINGMETHOD::AUTO)
CLog::LogF(LOGWARNING, "Shader preset selected but scaling method is not AUTO");
if (UsesShaderPreset())
return m_shaderPreset;
switch (m_scalingMethod)
{
case SCALINGMETHOD::NEAREST:
@ -73,17 +91,33 @@ void CRenderVideoSettings::SetVideoFilter(const std::string& videoFilter)
if (videoFilter == VIDEO_FILTER_NEAREST)
{
m_scalingMethod = SCALINGMETHOD::NEAREST;
ResetShaderPreset();
}
else if (videoFilter == VIDEO_FILTER_LINEAR)
{
m_scalingMethod = SCALINGMETHOD::LINEAR;
ResetShaderPreset();
}
else
{
m_scalingMethod = SCALINGMETHOD::AUTO;
// Not a known video filter, assume it's a shader preset path
SetShaderPreset(videoFilter);
}
}
void CRenderVideoSettings::ResetShaderPreset()
{
m_shaderPreset.clear();
}
void CRenderVideoSettings::ResetPixels()
{
m_pixelPath.clear();
}
bool CRenderVideoSettings::UsesShaderPreset() const
{
return !m_shaderPreset.empty() && m_scalingMethod == SCALINGMETHOD::AUTO;
}

View File

@ -43,6 +43,10 @@ public:
STRETCHMODE GetRenderStretchMode() const { return m_stretchMode; }
void SetRenderStretchMode(STRETCHMODE mode) { m_stretchMode = mode; }
const std::string& GetShaderPreset() const { return m_shaderPreset; }
void SetShaderPreset(const std::string& shaderPreset) { m_shaderPreset = shaderPreset; }
void ResetShaderPreset();
unsigned int GetRenderRotation() const { return m_rotationDegCCW; }
void SetRenderRotation(unsigned int rotationDegCCW) { m_rotationDegCCW = rotationDegCCW; }
@ -51,9 +55,12 @@ public:
void ResetPixels();
private:
bool UsesShaderPreset() const;
SCALINGMETHOD m_scalingMethod;
STRETCHMODE m_stretchMode;
unsigned int m_rotationDegCCW;
std::string m_shaderPreset;
std::string m_pixelPath;
};
} // namespace RETRO

View File

@ -12,6 +12,7 @@
#include "cores/RetroPlayer/buffers/IRenderBufferPool.h"
#include "cores/RetroPlayer/rendering/RenderContext.h"
#include "cores/RetroPlayer/rendering/RenderUtils.h"
#include "cores/RetroPlayer/shaders/IShaderPreset.h"
#include "utils/log.h"
using namespace KODI;
@ -23,7 +24,11 @@ using namespace RETRO;
CRPBaseRenderer::CRPBaseRenderer(const CRenderSettings& renderSettings,
CRenderContext& context,
std::shared_ptr<IRenderBufferPool> bufferPool)
: m_context(context), m_bufferPool(std::move(bufferPool)), m_renderSettings(renderSettings)
: m_context(context),
m_bufferPool(std::move(bufferPool)),
m_renderSettings(renderSettings),
m_bShadersNeedUpdate(true),
m_bUseShaderPreset(false)
{
m_bufferPool->RegisterRenderer(this);
}
@ -37,7 +42,22 @@ CRPBaseRenderer::~CRPBaseRenderer()
bool CRPBaseRenderer::IsCompatible(const CRenderVideoSettings& settings) const
{
return m_bufferPool->IsCompatible(settings);
if (!m_bufferPool->IsCompatible(settings))
return false;
// Shader preset must match
std::string shaderPreset;
if (m_shaderPreset)
shaderPreset = m_shaderPreset->GetShaderPreset();
// Shader preset might not be initialized yet
if (!shaderPreset.empty())
{
if (settings.GetShaderPreset() != shaderPreset)
return false;
}
return true;
}
bool CRPBaseRenderer::Configure(AVPixelFormat format)
@ -132,6 +152,15 @@ void CRPBaseRenderer::SetRenderRotation(unsigned int rotationDegCCW)
m_renderSettings.VideoSettings().SetRenderRotation(rotationDegCCW);
}
void CRPBaseRenderer::SetShaderPreset(const std::string& presetPath)
{
if (presetPath != m_renderSettings.VideoSettings().GetShaderPreset())
{
m_renderSettings.VideoSettings().SetShaderPreset(presetPath);
m_bShadersNeedUpdate = true;
}
}
void CRPBaseRenderer::SetPixels(const std::string& pixelPath)
{
m_renderSettings.VideoSettings().SetPixels(pixelPath);
@ -203,6 +232,10 @@ void CRPBaseRenderer::ManageRenderArea(const IRenderBuffer& renderBuffer)
// Adapt the drawing rect points if we have to rotate
m_rotatedDestCoords = CRenderUtils::ReorderDrawPoints(destRect, rotationDegCCW);
// Update video shader source size
if (m_shaderPreset)
m_shaderPreset->SetVideoSize(sourceWidth, sourceHeight);
}
void CRPBaseRenderer::MarkDirty()
@ -210,6 +243,21 @@ void CRPBaseRenderer::MarkDirty()
// CServiceBroker::GetGUI()->GetWindowManager().MarkDirty(m_dimensions); //! @todo
}
/**
* \brief Updates everything needed for video shaders (shader presets)
* Needs to be called after m_renderBuffer has been set
*/
void CRPBaseRenderer::Updateshaders()
{
if (m_bShadersNeedUpdate)
{
if (m_shaderPreset)
m_bUseShaderPreset =
m_shaderPreset->SetShaderPreset(m_renderSettings.VideoSettings().GetShaderPreset());
m_bShadersNeedUpdate = false;
}
}
void CRPBaseRenderer::PreRender(bool clear)
{
if (!m_bConfigured)
@ -219,6 +267,8 @@ void CRPBaseRenderer::PreRender(bool clear)
if (clear)
m_context.Clear(m_context.UseLimitedColor() ? UTILS::COLOR::LIMITED_BLACK
: UTILS::COLOR::BLACK);
// ManageRenderArea(*m_renderBuffer);
}
void CRPBaseRenderer::PostRender()

View File

@ -23,6 +23,11 @@ extern "C"
namespace KODI
{
namespace SHADER
{
class IShaderPreset;
}
namespace RETRO
{
class CRenderContext;
@ -67,6 +72,7 @@ public:
void SetScalingMethod(SCALINGMETHOD method);
void SetStretchMode(STRETCHMODE stretchMode);
void SetRenderRotation(unsigned int rotationDegCCW);
void SetShaderPreset(const std::string& presetPath);
void SetPixels(const std::string& pixelPath);
// Rendering properties
@ -95,6 +101,13 @@ protected:
CRect m_sourceRect;
std::array<CPoint, 4> m_rotatedDestCoords{};
// Video shaders
void Updateshaders();
std::unique_ptr<SHADER::IShaderPreset> m_shaderPreset;
bool m_bShadersNeedUpdate;
bool m_bUseShaderPreset;
private:
/*!
* \brief Calculate driven dimensions

View File

@ -66,7 +66,14 @@ CRenderBufferPoolGuiTexture::CRenderBufferPoolGuiTexture(SCALINGMETHOD scalingMe
bool CRenderBufferPoolGuiTexture::IsCompatible(const CRenderVideoSettings& renderSettings) const
{
return renderSettings.GetScalingMethod() == m_scalingMethod;
if (renderSettings.GetScalingMethod() != m_scalingMethod)
return false;
// Shaders not supported
if (!renderSettings.GetShaderPreset().empty())
return false;
return true;
}
IRenderBuffer* CRenderBufferPoolGuiTexture::CreateRenderBuffer(void* header /* = nullptr */)

View File

@ -11,7 +11,10 @@
#include "cores/RetroPlayer/rendering/RenderContext.h"
#include "cores/RetroPlayer/rendering/RenderTranslator.h"
#include "cores/RetroPlayer/rendering/RenderVideoSettings.h"
#include "cores/RetroPlayer/rendering/VideoShaders/windows/RPWinOutputShader.h"
#include "cores/RetroPlayer/shaders/windows/RPWinOutputShader.h"
#include "cores/RetroPlayer/shaders/windows/ShaderPresetDX.h"
#include "cores/RetroPlayer/shaders/windows/ShaderTextureDX.h"
#include "cores/RetroPlayer/shaders/windows/ShaderTextureDXRef.h"
#include "guilib/D3DResource.h"
#include "rendering/dx/RenderSystemDX.h"
#include "utils/log.h"
@ -60,7 +63,8 @@ CWinRenderBuffer::~CWinRenderBuffer()
bool CWinRenderBuffer::CreateTexture()
{
if (!m_intermediateTarget->Create(m_width, m_height, 1, D3D11_USAGE_DYNAMIC, m_targetDxFormat))
if (!m_intermediateTarget->GetTexture().Create(m_width, m_height, 1, D3D11_USAGE_DYNAMIC,
m_targetDxFormat))
{
CLog::Log(LOGERROR, "WinRenderer: Intermediate render target creation failed");
return false;
@ -73,7 +77,8 @@ bool CWinRenderBuffer::GetTexture(uint8_t*& data, unsigned int& stride)
{
// Scale and upload texture
D3D11_MAPPED_SUBRESOURCE destlr;
if (!m_intermediateTarget->LockRect(0, &destlr, D3D11_MAP_WRITE_DISCARD))
if (!m_intermediateTarget->GetTexture().LockRect(0, &destlr, D3D11_MAP_WRITE_DISCARD))
{
CLog::Log(LOGERROR, "WinRenderer: Failed to lock swtarget texture into memory");
return false;
@ -87,7 +92,7 @@ bool CWinRenderBuffer::GetTexture(uint8_t*& data, unsigned int& stride)
bool CWinRenderBuffer::ReleaseTexture()
{
if (!m_intermediateTarget->UnlockRect(0))
if (!m_intermediateTarget->GetTexture().UnlockRect(0))
{
CLog::Log(LOGERROR, "WinRenderer: Failed to unlock swtarget texture");
return false;
@ -110,7 +115,8 @@ bool CWinRenderBuffer::UploadTexture()
// Create intermediate texture
if (!m_intermediateTarget)
{
m_intermediateTarget.reset(new CD3DTexture);
m_intermediateTarget =
std::make_unique<SHADER::CShaderTextureDX>(std::make_shared<CD3DTexture>());
if (!CreateTexture())
{
m_intermediateTarget.reset();
@ -176,6 +182,13 @@ CWinRenderBufferPool::CWinRenderBufferPool()
bool CWinRenderBufferPool::IsCompatible(const CRenderVideoSettings& renderSettings) const
{
//! @todo Move this logic to generic class
// Shader presets are compatible
if (!renderSettings.GetShaderPreset().empty())
return true;
// If no shader preset is specified, scaling methods must match
return GetShader(renderSettings.GetScalingMethod()) != nullptr;
}
@ -197,7 +210,7 @@ bool CWinRenderBufferPool::ConfigureDX()
return true;
}
CRPWinOutputShader* CWinRenderBufferPool::GetShader(SCALINGMETHOD scalingMethod) const
SHADER::CRPWinOutputShader* CWinRenderBufferPool::GetShader(SCALINGMETHOD scalingMethod) const
{
auto it = m_outputShaders.find(scalingMethod);
@ -221,7 +234,9 @@ void CWinRenderBufferPool::CompileOutputShaders()
{
for (auto scalingMethod : GetScalingMethods())
{
std::unique_ptr<CRPWinOutputShader> outputShader(new CRPWinOutputShader);
std::unique_ptr<SHADER::CRPWinOutputShader> outputShader =
std::make_unique<SHADER::CRPWinOutputShader>();
if (outputShader->Create(scalingMethod))
m_outputShaders[scalingMethod] = std::move(outputShader);
else
@ -237,6 +252,8 @@ CRPWinRenderer::CRPWinRenderer(const CRenderSettings& renderSettings,
std::shared_ptr<IRenderBufferPool> bufferPool)
: CRPBaseRenderer(renderSettings, context, std::move(bufferPool))
{
// Initialize CRPBaseRenderer fields
m_shaderPreset = std::make_unique<SHADER::CShaderPresetDX>(m_context);
}
bool CRPWinRenderer::ConfigureInternal()
@ -280,26 +297,65 @@ void CRPWinRenderer::Render(CD3DTexture& target)
const CPoint destPoints[4] = {m_rotatedDestCoords[0], m_rotatedDestCoords[1],
m_rotatedDestCoords[2], m_rotatedDestCoords[3]};
if (m_renderBuffer != nullptr)
CWinRenderBuffer* renderBuffer = static_cast<CWinRenderBuffer*>(m_renderBuffer);
if (renderBuffer == nullptr)
return;
SHADER::CShaderTextureDX* renderBufferTarget = renderBuffer->GetTarget();
if (renderBufferTarget == nullptr)
return;
Updateshaders();
// Are we using video shaders?
if (m_bUseShaderPreset)
{
CD3DTexture* intermediateTarget = static_cast<CWinRenderBuffer*>(m_renderBuffer)->GetTarget();
if (intermediateTarget != nullptr)
//! @todo Orientation?
/*
CPoint destPoints[4];
// select destination rectangle
if (m_renderOrientation)
{
CRect viewPort;
m_context.GetViewPort(viewPort);
for (size_t i = 0; i < 4; ++i)
destPoints[i] = m_rotatedDestCoords[i];
}
else
{
CRect destRect = m_context.StereoCorrection(m_renderSettings.Geometry().Dimensions());
destPoints[0] = { destRect.x1, destRect.y1 };
destPoints[1] = { destRect.x2, destRect.y1 };
destPoints[2] = { destRect.x2, destRect.y2 };
destPoints[3] = { destRect.x1, destRect.y2 };
}
*/
// Pick appropriate output shader depending on the scaling method of the renderer
SCALINGMETHOD scalingMethod = m_renderSettings.VideoSettings().GetScalingMethod();
SHADER::CShaderTextureDXRef targetTexture{target};
CWinRenderBufferPool* bufferPool = static_cast<CWinRenderBufferPool*>(m_bufferPool.get());
CRPWinOutputShader* outputShader = bufferPool->GetShader(scalingMethod);
// Render shaders and ouput to display
if (!m_shaderPreset->RenderUpdate(destPoints, *renderBufferTarget, targetTexture))
{
m_bShadersNeedUpdate = false;
m_bUseShaderPreset = false;
}
}
else // Not using video shaders, output using output shader
{
CD3DTexture& intermediateTarget = renderBufferTarget->GetTexture();
// Use the picked output shader to render to the target
if (outputShader != nullptr)
{
outputShader->Render(*intermediateTarget, m_sourceRect, destPoints, viewPort, &target,
m_context.UseLimitedColor() ? 1 : 0);
}
CRect viewPort;
m_context.GetViewPort(viewPort);
// Pick appropriate output shader depending on the scaling method of the renderer
SCALINGMETHOD scalingMethod = m_renderSettings.VideoSettings().GetScalingMethod();
CWinRenderBufferPool* bufferPool = static_cast<CWinRenderBufferPool*>(m_bufferPool.get());
SHADER::CRPWinOutputShader* outputShader = bufferPool->GetShader(scalingMethod);
// Use the picked output shader to render to the target
if (outputShader != nullptr)
{
outputShader->Render(intermediateTarget, m_sourceRect, destPoints, viewPort, target,
m_context.UseLimitedColor() ? 1 : 0);
}
}
}

View File

@ -12,7 +12,9 @@
#include "cores/RetroPlayer/buffers/BaseRenderBufferPool.h"
#include "cores/RetroPlayer/buffers/video/RenderBufferSysMem.h"
#include "cores/RetroPlayer/process/RPProcessInfo.h"
#include "cores/RetroPlayer/shaders/windows/RPWinOutputShader.h"
#include <map>
#include <memory>
#include <stdint.h>
#include <vector>
@ -24,6 +26,11 @@ struct SwsContext;
namespace KODI
{
namespace SHADER
{
class CShaderTextureDX;
} // namespace SHADER
namespace RETRO
{
class CRenderContext;
@ -51,7 +58,7 @@ public:
// implementation of IRenderBuffer via CRenderBufferSysMem
bool UploadTexture() override;
CD3DTexture* GetTarget() { return m_intermediateTarget.get(); }
SHADER::CShaderTextureDX* GetTarget() { return m_intermediateTarget.get(); }
private:
bool CreateTexture();
@ -71,7 +78,7 @@ private:
const DXGI_FORMAT m_targetDxFormat;
AVPixelFormat m_targetPixFormat;
std::unique_ptr<CD3DTexture> m_intermediateTarget;
std::unique_ptr<SHADER::CShaderTextureDX> m_intermediateTarget;
SwsContext* m_swsContext = nullptr;
};
@ -90,7 +97,7 @@ public:
// DirectX interface
bool ConfigureDX();
CRPWinOutputShader* GetShader(SCALINGMETHOD scalingMethod) const;
SHADER::CRPWinOutputShader* GetShader(SCALINGMETHOD scalingMethod) const;
private:
static const std::vector<SCALINGMETHOD>& GetScalingMethods();
@ -98,7 +105,7 @@ private:
void CompileOutputShaders();
DXGI_FORMAT m_targetDxFormat = DXGI_FORMAT_UNKNOWN;
std::map<SCALINGMETHOD, std::unique_ptr<CRPWinOutputShader>> m_outputShaders;
std::map<SCALINGMETHOD, std::unique_ptr<SHADER::CRPWinOutputShader>> m_outputShaders;
};
class CRPWinRenderer : public CRPBaseRenderer

View File

@ -1,3 +0,0 @@
if(NOT ENABLE_STATIC_LIBS)
core_add_library(rp-videoshaders)
endif()

View File

@ -1,5 +0,0 @@
set(SOURCES RPWinOutputShader.cpp)
set(HEADERS RPWinOutputShader.h)
core_add_library(rp-videoshaders-windows)

View File

@ -1,120 +0,0 @@
/*
* Copyright (C) 2017-2018 Team Kodi
* This file is part of Kodi - https://kodi.tv
*
* SPDX-License-Identifier: GPL-2.0-or-later
* See LICENSES/README.md for more information.
*/
#include "RPWinOutputShader.h"
#include "utils/log.h"
using namespace KODI;
using namespace RETRO;
bool CRPWinOutputShader::Create(SCALINGMETHOD scalingMethod)
{
CWinShader::CreateVertexBuffer(4, sizeof(CUSTOMVERTEX));
DefinesMap defines;
switch (scalingMethod)
{
case SCALINGMETHOD::NEAREST:
defines["SAMP_NEAREST"] = "";
break;
case SCALINGMETHOD::LINEAR:
default:
break;
}
std::string effectPath("special://xbmc/system/shaders/rp_output_d3d.fx");
if (!LoadEffect(effectPath, &defines))
{
CLog::LogF(LOGERROR, "Failed to load shader {}.", effectPath);
return false;
}
// Create input layout
D3D11_INPUT_ELEMENT_DESC layout[] = {
{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
{"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0},
};
return CWinShader::CreateInputLayout(layout, ARRAYSIZE(layout));
}
void CRPWinOutputShader::Render(CD3DTexture& sourceTexture,
CRect sourceRect,
const CPoint points[4],
CRect& viewPort,
CD3DTexture* target,
unsigned range)
{
PrepareParameters(sourceTexture.GetWidth(), sourceTexture.GetHeight(), sourceRect, points);
SetShaderParameters(sourceTexture, range, viewPort);
Execute({target}, 4);
}
void CRPWinOutputShader::PrepareParameters(unsigned sourceWidth,
unsigned sourceHeight,
CRect sourceRect,
const CPoint points[4])
{
bool changed = false;
for (int i = 0; i < 4 && !changed; ++i)
changed = points[i] != m_destPoints[i];
if (m_sourceWidth != sourceWidth || m_sourceHeight != sourceHeight ||
m_sourceRect != sourceRect || changed)
{
m_sourceWidth = sourceWidth;
m_sourceHeight = sourceHeight;
m_sourceRect = sourceRect;
for (int i = 0; i < 4; ++i)
m_destPoints[i] = points[i];
CUSTOMVERTEX* v = nullptr;
CWinShader::LockVertexBuffer(static_cast<void**>(static_cast<void*>(&v)));
v[0].x = m_destPoints[0].x;
v[0].y = m_destPoints[0].y;
v[0].z = 0.0f;
v[0].tu = m_sourceRect.x1 / m_sourceWidth;
v[0].tv = m_sourceRect.y1 / m_sourceHeight;
v[1].x = m_destPoints[1].x;
v[1].y = m_destPoints[1].y;
v[1].z = 0.0f;
v[1].tu = m_sourceRect.x2 / m_sourceWidth;
v[1].tv = m_sourceRect.y1 / m_sourceHeight;
v[2].x = m_destPoints[2].x;
v[2].y = m_destPoints[2].y;
v[2].z = 0.0f;
v[2].tu = m_sourceRect.x2 / m_sourceWidth;
v[2].tv = m_sourceRect.y2 / m_sourceHeight;
v[3].x = m_destPoints[3].x;
v[3].y = m_destPoints[3].y;
v[3].z = 0.0f;
v[3].tu = m_sourceRect.x1 / m_sourceWidth;
v[3].tv = m_sourceRect.y2 / m_sourceHeight;
CWinShader::UnlockVertexBuffer();
}
}
void CRPWinOutputShader::SetShaderParameters(CD3DTexture& sourceTexture,
unsigned range,
CRect& viewPort)
{
m_effect.SetTechnique("OUTPUT_T");
m_effect.SetResources("g_Texture", sourceTexture.GetAddressOfSRV(), 1);
float viewPortArray[2] = {viewPort.Width(), viewPort.Height()};
m_effect.SetFloatArray("g_viewPort", viewPortArray, 2);
float params[3] = {static_cast<float>(range)};
m_effect.SetFloatArray("m_params", params, 1);
}

View File

@ -1,61 +0,0 @@
/*
* Copyright (C) 2017-2018 Team Kodi
* This file is part of Kodi - https://kodi.tv
*
* SPDX-License-Identifier: GPL-2.0-or-later
* See LICENSES/README.md for more information.
*/
#pragma once
#include "cores/GameSettings.h"
#include "cores/VideoPlayer/VideoRenderers/VideoShaders/WinVideoFilter.h"
namespace KODI
{
namespace RETRO
{
class CRPWinOutputShader : public CWinShader
{
public:
~CRPWinOutputShader() = default;
bool Create(SCALINGMETHOD scalingMethod);
void Render(CD3DTexture& sourceTexture,
CRect sourceRect,
const CPoint points[4],
CRect& viewPort,
CD3DTexture* target,
unsigned range = 0);
private:
void PrepareParameters(unsigned sourceWidth,
unsigned sourceHeight,
CRect sourceRect,
const CPoint points[4]);
void SetShaderParameters(CD3DTexture& sourceTexture, unsigned range, CRect& viewPort);
unsigned m_sourceWidth{0};
unsigned m_sourceHeight{0};
CRect m_sourceRect{0.f, 0.f, 0.f, 0.f};
CPoint m_destPoints[4] = {
{0.f, 0.f},
{0.f, 0.f},
{0.f, 0.f},
{0.f, 0.f},
};
struct CUSTOMVERTEX
{
FLOAT x;
FLOAT y;
FLOAT z;
FLOAT tu;
FLOAT tv;
};
};
} // namespace RETRO
} // namespace KODI

View File

@ -0,0 +1,18 @@
set(SOURCES ShaderPreset.cpp
ShaderPresetFactory.cpp
ShaderUtils.cpp
)
set(HEADERS IShader.h
IShaderLut.h
IShaderPreset.h
IShaderPresetLoader.h
IShaderSampler.h
IShaderTexture.h
ShaderPreset.h
ShaderPresetFactory.h
ShaderTypes.h
ShaderUtils.h
)
core_add_library(rp-shaders)

View File

@ -0,0 +1,98 @@
/*
* Copyright (C) 2017-2025 Team Kodi
* This file is part of Kodi - https://kodi.tv
*
* SPDX-License-Identifier: GPL-2.0-or-later
* See LICENSES/README.md for more information.
*/
#pragma once
#include "ShaderTypes.h"
#include "utils/Geometry.h"
#include <map>
#include <stdint.h>
#include <string>
#include <vector>
namespace KODI
{
namespace SHADER
{
class IShaderLut;
class IShaderTexture;
class IShader
{
public:
virtual ~IShader() = default;
/*!
* \brief Construct the video shader instance
*
* \param shaderSource Source code of the shader (both vertex and pixel/fragment)
* \param shaderPath Full path to the shader file
* \param shaderParameters Struct with all parameters pertaining to the shader
* \param luts Look-up textures pertaining to the shader
* \param viewPortSize Size of the window/viewport
* \param passIdx Index of the video shader pass
* \param frameCountMod Modulo applied to the frame count before sendign it to the shader
*
* \return Returns false if creating the shader failed, true otherwise
*/
virtual bool Create(std::string shaderSource,
std::string shaderPath,
ShaderParameterMap shaderParameters,
std::vector<std::shared_ptr<IShaderLut>> luts,
float2 viewPortSize,
unsigned int passIdx,
unsigned int frameCountMod = 0) = 0;
/*!
* \brief Renders the video shader to the target texture
*
* \param source Source texture to pass to the shader as input
* \param target Target texture to render the shader to
*/
virtual void Render(IShaderTexture& source, IShaderTexture& target) = 0;
/*!
* \brief Sets the input and output sizes in pixels
*
* \param prevSize Input image size of the shader in pixels
* \param prevTextureSize Power-of-two input texture size in pixels
* \param nextSize Output image size of the shader in pixels
*/
virtual void SetSizes(const float2& prevSize,
const float2& prevTextureSize,
const float2& nextSize) = 0;
/*!
* \brief Called before rendering
*
* Updates any internal state needed to ensure that correct data is passed to
* the shader when rendering.
*
* \param dest Coordinates of the 4 corners of the output viewport/window
* \param sourceTexture Source texture of the first shader pass
* \param pShaderTextures Intermediate textures used for all shader passes
* \param pShaders All shader passes
* \param frameCount Number of frames that have passed
*/
virtual void PrepareParameters(
CPoint dest[4],
IShaderTexture& sourceTexture,
const std::vector<std::unique_ptr<IShaderTexture>>& pShaderTextures,
const std::vector<std::unique_ptr<IShader>>& pShaders,
uint64_t frameCount) = 0;
/*!
* \brief Updates the model view projection matrix
*
* Should usually only be called when the viewport/window size changes.
*/
virtual void UpdateMVP() = 0;
};
} // namespace SHADER
} // namespace KODI

View File

@ -0,0 +1,74 @@
/*
* Copyright (C) 2017-2025 Team Kodi
* This file is part of Kodi - https://kodi.tv
*
* SPDX-License-Identifier: GPL-2.0-or-later
* See LICENSES/README.md for more information.
*/
#pragma once
#include "ShaderTypes.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
class CTexture;
namespace KODI
{
namespace RETRO
{
class CRenderContext;
}
namespace SHADER
{
/*!
* \brief A lookup table to apply color transforms in a shader
*/
class IShaderLut
{
public:
IShaderLut(std::string id, std::string path) : m_id(std::move(id)), m_path(std::move(path)) {}
virtual ~IShaderLut() = default;
/*!
* \brief Create the LUT and allocate resources
*
* \param context The render context
* \param lut The LUT information structure
*
* \return Returns true if successful and the LUT can be used, false otherwise
*/
virtual bool Create(RETRO::CRenderContext& context, const ShaderLut& lut) = 0;
/*!
* \brief Gets ID of LUT
*
* \return Returns unique name (ID) of look up texture
*/
const std::string& GetID() const { return m_id; }
/*!
* \brief Gets full path of LUT
*
* \return Returns full path of look up texture
*/
const std::string& GetPath() const { return m_path; }
/*!
* \brief Gets texture of LUT
*
* \return Returns pointer to the texture where the LUT data is stored in
*/
virtual CTexture* GetTexture() = 0;
protected:
std::string m_id;
std::string m_path;
};
} // namespace SHADER
} // namespace KODI

View File

@ -0,0 +1,92 @@
/*
* Copyright (C) 2017-2025 Team Kodi
* This file is part of Kodi - https://kodi.tv
*
* SPDX-License-Identifier: GPL-2.0-or-later
* See LICENSES/README.md for more information.
*/
#pragma once
#include "ShaderTypes.h"
#include "utils/Geometry.h"
#include <string>
#include <vector>
namespace KODI
{
namespace SHADER
{
class IShaderTexture;
class IShaderPreset
{
public:
virtual ~IShaderPreset() = default;
//! @todo Implement once and for all
/*!
* \brief Reads/parses a shader preset file and loads its state to the object
*
* The state is implementation specific.
*
* \param presetPath Full path of the preset file
*
* \return Returns true on successful parsing, false on failed
*/
virtual bool ReadPresetFile(const std::string& presetPath) = 0;
/*!
* \brief Updates state if needed and renderes the preset to the target texture
*
* \param dest Coordinates of the 4 corners of the output viewport/window
* \param source The source of the video frame, in its original resolution (unscaled)
* \param target The target texture that the final result will be rendered to
*
* \return Returns false if updating or rendering failed, true if both succeeded
*/
virtual bool RenderUpdate(const CPoint dest[],
IShaderTexture& source,
IShaderTexture& target) = 0;
/*!
* \brief Informs about the speed of playback
*
* \param speed Commonly 1.0 for normal playback, and 0.0 if paused
*/
virtual void SetSpeed(double speed) = 0;
/*!
* \brief Size of the input/source frame in pixels
*
* \param videoWidth Height of the source frame in pixels
* \param videoHeight Height of the source frame in pixels
*/
virtual void SetVideoSize(unsigned int videoWidth, unsigned int videoHeight) = 0;
/*!
* \brief Set the preset to be rendered on the next frame
*
* \param shaderPresetPath Full path to the preset file to be loaded
*
* \return Returns false if loading the preset failed, true otherwise
*/
virtual bool SetShaderPreset(const std::string& shaderPresetPath) = 0;
/*!
* \brief Gets the full path to the shader preset
*
* \return The full path to the currently loaded preset file
*/
virtual const std::string& GetShaderPreset() const = 0;
/*!
* \brief Gets the passes of the loaded preset
*
* \return All the video shader passes of the currently loaded preset
*/
virtual std::vector<ShaderPass>& GetPasses() = 0;
};
} // namespace SHADER
} // namespace KODI

View File

@ -0,0 +1,38 @@
/*
* Copyright (C) 2017-2025 Team Kodi
* This file is part of Kodi - https://kodi.tv
*
* SPDX-License-Identifier: GPL-2.0-or-later
* See LICENSES/README.md for more information.
*/
#pragma once
#include <string>
namespace KODI
{
namespace SHADER
{
class IShaderPreset;
/*!
* \brief API for a class that can load shader presets
*/
class IShaderPresetLoader
{
public:
virtual ~IShaderPresetLoader() = default;
/*!
* \brief Load a shader preset from the given path
*
* \param presetPath The path to the shader preset
* \param shaderPreset The loaded shader preset, or untouched if false is returned
*
* \return True if the preset was loaded, false otherwise
*/
virtual bool LoadPreset(const std::string& presetPath, IShaderPreset& shaderPreset) = 0;
};
} // namespace SHADER
} // namespace KODI

View File

@ -0,0 +1,21 @@
/*
* Copyright (C) 2017-2025 Team Kodi
* This file is part of Kodi - https://kodi.tv
*
* SPDX-License-Identifier: GPL-2.0-or-later
* See LICENSES/README.md for more information.
*/
#pragma once
namespace KODI
{
namespace SHADER
{
class IShaderSampler
{
public:
virtual ~IShaderSampler() = default;
};
} // namespace SHADER
} // namespace KODI

View File

@ -0,0 +1,41 @@
/*
* Copyright (C) 2017-2025 Team Kodi
* This file is part of Kodi - https://kodi.tv
*
* SPDX-License-Identifier: GPL-2.0-or-later
* See LICENSES/README.md for more information.
*/
#pragma once
namespace KODI
{
namespace SHADER
{
/*!
* \brief Interface for a shader texture
*
* The interface is left relatively generic. The implementation should be able
* to manage any resources required by the texture.
*/
class IShaderTexture
{
public:
virtual ~IShaderTexture() = default;
/*!
* \brief Return width of texture
*
* \return Width of the texture in texels
*/
virtual float GetWidth() const = 0;
/*!
* \brief Return height of texture
*
* \return Height of the texture in texels
*/
virtual float GetHeight() const = 0;
};
} // namespace SHADER
} // namespace KODI

View File

@ -0,0 +1,265 @@
/*
* Copyright (C) 2017-2025 Team Kodi
* This file is part of Kodi - https://kodi.tv
*
* SPDX-License-Identifier: GPL-2.0-or-later
* See LICENSES/README.md for more information.
*/
#include "ShaderPreset.h"
#include "IShader.h"
#include "IShaderTexture.h"
#include "ServiceBroker.h"
#include "ShaderPresetFactory.h"
#include "cores/RetroPlayer/rendering/RenderContext.h"
#include "games/GameServices.h"
#include "utils/URIUtils.h"
#include "utils/log.h"
#include <regex>
using namespace KODI;
using namespace SHADER;
CShaderPreset::CShaderPreset(RETRO::CRenderContext& context,
unsigned videoWidth,
unsigned videoHeight)
: m_context(context), m_videoSize(videoWidth, videoHeight)
{
CRect viewPort;
m_context.GetViewPort(viewPort);
m_outputSize = {viewPort.Width(), viewPort.Height()};
}
CShaderPreset::~CShaderPreset()
{
DisposeShaders();
// The GUI is going to render after this, so apply the state required
m_context.ApplyStateBlock();
}
bool CShaderPreset::ReadPresetFile(const std::string& presetPath)
{
return CServiceBroker::GetGameServices().VideoShaders().LoadPreset(presetPath, *this);
}
bool CShaderPreset::RenderUpdate(const CPoint dest[],
IShaderTexture& source,
IShaderTexture& target)
{
// Save the viewport
CRect viewPort;
m_context.GetViewPort(viewPort);
// Handle resizing of the viewport (window)
UpdateViewPort(viewPort);
// Update shaders/shader textures if required
if (!Update())
return false;
PrepareParameters(dest, source, target);
const unsigned int numPasses = static_cast<unsigned int>(m_pShaders.size());
// Apply all passes except the last one (which needs to be applied to the backbuffer)
IShaderTexture* sourceTexture = &source;
for (unsigned shaderIdx = 0; shaderIdx + 1 < numPasses; ++shaderIdx)
{
IShader& shader = *m_pShaders[shaderIdx];
IShaderTexture& texture = *m_pShaderTextures[shaderIdx];
RenderShader(shader, *sourceTexture, texture);
sourceTexture = &texture;
}
// Restore our viewport
m_context.SetViewPort(viewPort);
m_context.SetScissors(viewPort);
// Apply the last pass and write to target (backbuffer) instead of the last texture
IShader* lastShader = m_pShaders.back().get();
lastShader->Render(*sourceTexture, target);
m_frameCount += static_cast<float>(m_speed);
return true;
}
void CShaderPreset::SetVideoSize(unsigned int videoWidth, unsigned int videoHeight)
{
if (videoWidth != static_cast<unsigned int>(m_videoSize.x) ||
videoHeight != static_cast<unsigned int>(m_videoSize.y))
{
m_videoSize = {videoWidth, videoHeight};
m_bPresetNeedsUpdate = true;
}
}
bool CShaderPreset::SetShaderPreset(const std::string& shaderPresetPath)
{
m_bPresetNeedsUpdate = true;
m_presetPath = shaderPresetPath;
return Update();
}
const std::string& CShaderPreset::GetShaderPreset() const
{
return m_presetPath;
}
bool CShaderPreset::Update()
{
auto updateFailed = [this](const std::string& msg)
{
m_failedPaths.insert(m_presetPath);
CLog::Log(LOGWARNING, "CShaderPreset::Update: {}", msg);
DisposeShaders();
return false;
};
if (m_bPresetNeedsUpdate && !HasPathFailed(m_presetPath))
{
DisposeShaders();
if (m_presetPath.empty())
// No preset should load, just return false, we shouldn't add "" to the failed paths
return false;
if (!ReadPresetFile(m_presetPath))
{
CLog::Log(
LOGERROR,
"CShaderPreset::Update: Couldn't load shader preset {} or the shaders it references",
m_presetPath);
return false;
}
if (!CreateShaders())
return updateFailed("Failed to initialize shaders");
if (!CreateLayouts())
return updateFailed("Failed to create layouts");
if (!CreateBuffers())
return updateFailed("Failed to initialize buffers");
if (!CreateShaderTextures())
return updateFailed("A shader texture failed to init");
if (!CreateSamplers())
return updateFailed("Failed to create samplers");
}
if (m_pShaders.empty())
return false;
// Each pass except the last one must have its own texture and the opposite is also true
if (m_pShaders.size() != m_pShaderTextures.size() + 1)
return updateFailed("A shader or texture failed to init");
m_bPresetNeedsUpdate = false;
return true;
}
void CShaderPreset::UpdateViewPort()
{
CRect viewPort;
m_context.GetViewPort(viewPort);
UpdateViewPort(viewPort);
}
void CShaderPreset::UpdateViewPort(CRect viewPort)
{
const float2 currentViewPortSize = {viewPort.Width(), viewPort.Height()};
if (currentViewPortSize != m_outputSize)
{
m_outputSize = currentViewPortSize;
m_bPresetNeedsUpdate = true;
Update();
}
}
void CShaderPreset::UpdateMVPs()
{
for (std::unique_ptr<IShader>& videoShader : m_pShaders)
videoShader->UpdateMVP();
}
void CShaderPreset::PrepareParameters(const CPoint dest[],
IShaderTexture& source,
IShaderTexture& target)
{
if (m_dest[0] != dest[0] || m_dest[1] != dest[1] || m_dest[2] != dest[2] ||
m_dest[3] != dest[3] || target.GetWidth() != m_outputSize.x ||
target.GetHeight() != m_outputSize.y)
{
for (size_t i = 0; i < 4; ++i)
m_dest[i] = dest[i];
m_outputSize = {target.GetWidth(), target.GetHeight()};
// Update projection matrix and update video shaders
UpdateMVPs();
UpdateViewPort();
}
const unsigned int numPasses = static_cast<unsigned int>(m_pShaders.size());
// Prepare parameters for all shader passes
for (unsigned int shaderIdx = 0; shaderIdx < numPasses; ++shaderIdx)
{
std::unique_ptr<IShader>& videoShader = m_pShaders[shaderIdx];
videoShader->PrepareParameters(m_dest, source, m_pShaderTextures, m_pShaders,
static_cast<uint64_t>(m_frameCount));
}
}
void CShaderPreset::DisposeShaders()
{
m_pShaders.clear();
m_pShaderTextures.clear();
m_passes.clear();
m_bPresetNeedsUpdate = true;
}
bool CShaderPreset::HasPathFailed(const std::string& path) const
{
return m_failedPaths.find(path) != m_failedPaths.end();
}
ShaderParameterMap CShaderPreset::GetShaderParameters(
const std::vector<ShaderParameter>& parameters, const std::string& sourceStr) const
{
static const std::regex pragmaParamRegex("#pragma parameter ([a-zA-Z_][a-zA-Z0-9_]*)");
std::smatch matches;
std::vector<std::string> validParams;
std::string::const_iterator searchStart(sourceStr.cbegin());
while (regex_search(searchStart, sourceStr.cend(), matches, pragmaParamRegex))
{
validParams.push_back(matches[1].str());
searchStart += matches.position() + matches.length();
}
ShaderParameterMap matchParams;
// For each param found in the source code
for (const std::string& match : validParams)
{
// For each param found in the preset file
for (const ShaderParameter& parameter : parameters)
{
// Check if they match
if (match == parameter.strId)
{
// The add-on has already handled parsing and overwriting default
// parameter values from the preset file. The final value we
// should use is in the 'current' field.
matchParams[match] = parameter.current;
break;
}
}
}
return matchParams;
}

View File

@ -0,0 +1,110 @@
/*
* Copyright (C) 2017-2025 Team Kodi
* This file is part of Kodi - https://kodi.tv
*
* SPDX-License-Identifier: GPL-2.0-or-later
* See LICENSES/README.md for more information.
*/
#pragma once
#include "cores/RetroPlayer/shaders/IShaderPreset.h"
#include "cores/RetroPlayer/shaders/ShaderTypes.h"
#include "utils/Geometry.h"
#include <memory>
#include <set>
#include <string>
#include <vector>
namespace KODI
{
namespace RETRO
{
class CRenderContext;
}
namespace SHADER
{
class IShader;
class IShaderTexture;
class CShaderPreset : public IShaderPreset
{
public:
// Instance of CShaderPreset
explicit CShaderPreset(RETRO::CRenderContext& context,
unsigned videoWidth = 0,
unsigned videoHeight = 0);
~CShaderPreset() override;
// Implementation of IShaderPreset
bool ReadPresetFile(const std::string& presetPath) override;
bool RenderUpdate(const CPoint dest[], IShaderTexture& source, IShaderTexture& target) override;
void SetSpeed(double speed) override { m_speed = speed; }
void SetVideoSize(unsigned int videoWidth, unsigned int videoHeight) override;
bool SetShaderPreset(const std::string& shaderPresetPath) override;
const std::string& GetShaderPreset() const override;
std::vector<ShaderPass>& GetPasses() override { return m_passes; }
protected:
// Shader interface
virtual bool CreateShaders() = 0;
virtual bool CreateLayouts() = 0;
virtual bool CreateBuffers() = 0;
virtual bool CreateShaderTextures() = 0;
virtual bool CreateSamplers() = 0;
virtual void RenderShader(IShader& shader, IShaderTexture& source, IShaderTexture& target) = 0;
// Helper functions
bool Update();
void UpdateViewPort();
void UpdateViewPort(CRect viewPort);
void UpdateMVPs();
void PrepareParameters(const CPoint dest[], IShaderTexture& source, IShaderTexture& target);
void DisposeShaders();
bool HasPathFailed(const std::string& path) const;
ShaderParameterMap GetShaderParameters(const std::vector<ShaderParameter>& parameters,
const std::string& sourceStr) const;
// Construction parameters
RETRO::CRenderContext& m_context;
// Relative path of the currently loaded shader preset
// If empty, it means that a preset is not currently loaded
std::string m_presetPath;
// Set of paths of presets that are known to not load correctly
// Should not contain "" (empty path) because this signifies that a preset is not loaded
std::set<std::string> m_failedPaths;
// All video shader passes of the currently loaded preset
std::vector<ShaderPass> m_passes;
// Video shaders for the shader passes
std::vector<std::unique_ptr<IShader>> m_pShaders;
// Intermediate textures used for pixel shader passes
std::vector<std::unique_ptr<IShaderTexture>> m_pShaderTextures;
// Was the shader preset changed during the last frame?
bool m_bPresetNeedsUpdate = true;
// Size of the viewport
float2 m_outputSize;
// Size of the actual source video data (ie. 160x144 for the Game Boy)
float2 m_videoSize;
// Array of vertices that comprise the full viewport
CPoint m_dest[4];
// Number of frames that have passed
float m_frameCount = 0.0f;
// Playback speed
double m_speed = 1.0;
};
} // namespace SHADER
} // namespace KODI

View File

@ -0,0 +1,151 @@
/*
* Copyright (C) 2017-2025 Team Kodi
* This file is part of Kodi - https://kodi.tv
*
* SPDX-License-Identifier: GPL-2.0-or-later
* See LICENSES/README.md for more information.
*/
#include "ShaderPresetFactory.h"
#include "IShaderPresetLoader.h"
#include "addons/AddonEvents.h"
#include "addons/AddonManager.h"
#include "addons/ShaderPreset.h"
#include "addons/addoninfo/AddonInfo.h"
#include "addons/addoninfo/AddonType.h"
#include "utils/URIUtils.h"
#include "utils/log.h"
#include <algorithm>
#include <string>
using namespace KODI;
using namespace SHADER;
CShaderPresetFactory::CShaderPresetFactory(ADDON::CAddonMgr& addons) : m_addons(addons)
{
UpdateAddons();
m_addons.Events().Subscribe(this, &CShaderPresetFactory::OnEvent);
}
CShaderPresetFactory::~CShaderPresetFactory()
{
m_addons.Events().Unsubscribe(this);
}
void CShaderPresetFactory::RegisterLoader(IShaderPresetLoader* loader, const std::string& extension)
{
if (!extension.empty())
{
std::string strExtension = extension;
// Canonicalize extension with leading "."
if (extension[0] != '.')
strExtension.insert(strExtension.begin(), '.');
m_loaders.insert(std::make_pair(std::move(strExtension), loader));
}
}
void CShaderPresetFactory::UnregisterLoader(IShaderPresetLoader* loader)
{
for (auto it = m_loaders.begin(); it != m_loaders.end();)
{
if (it->second == loader)
m_loaders.erase(it++);
else
++it;
}
}
bool CShaderPresetFactory::LoadPreset(const std::string& presetPath, IShaderPreset& shaderPreset)
{
bool bSuccess = false;
std::string extension = URIUtils::GetExtension(presetPath);
if (!extension.empty())
{
auto itLoader = m_loaders.find(extension);
if (itLoader != m_loaders.end())
bSuccess = itLoader->second->LoadPreset(presetPath, shaderPreset);
}
return bSuccess;
}
bool CShaderPresetFactory::CanLoadPreset(const std::string& presetPath)
{
bool bSuccess = false;
std::string extension = URIUtils::GetExtension(presetPath);
if (!extension.empty())
bSuccess = (m_loaders.find(extension) != m_loaders.end());
return bSuccess;
}
void CShaderPresetFactory::OnEvent(const ADDON::AddonEvent& event)
{
if (typeid(event) == typeid(ADDON::AddonEvents::Enabled) ||
typeid(event) == typeid(ADDON::AddonEvents::Disabled) ||
typeid(event) == typeid(ADDON::AddonEvents::UnInstalled) ||
typeid(event) == typeid(ADDON::AddonEvents::ReInstalled))
{
UpdateAddons();
}
}
void CShaderPresetFactory::UpdateAddons()
{
using namespace ADDON;
std::vector<AddonInfoPtr> addonInfo;
m_addons.GetAddonInfos(addonInfo, true, AddonType::SHADERDLL);
// Look for removed/disabled add-ons
auto oldAddons = std::move(m_shaderAddons);
for (auto it = oldAddons.begin(); it != oldAddons.end(); ++it)
{
const std::string& addonId = it->first;
std::unique_ptr<ADDON::CShaderPresetAddon>& shaderAddon = it->second;
const bool bIsDisabled =
std::find_if(addonInfo.begin(), addonInfo.end(), [&addonId](const AddonInfoPtr& addon)
{ return addonId == addon->ID(); }) == addonInfo.end();
if (bIsDisabled)
UnregisterLoader(shaderAddon.get());
else
m_shaderAddons.emplace(addonId, std::move(shaderAddon));
}
// Look for new add-ons
for (const AddonInfoPtr& shaderAddon : addonInfo)
{
std::string addonId = shaderAddon->ID();
const bool bIsNew = (m_shaderAddons.find(addonId) == m_shaderAddons.end());
if (!bIsNew)
continue;
const bool bIsFailed = (m_failedAddons.find(addonId) != m_failedAddons.end());
if (bIsFailed)
continue;
std::unique_ptr<CShaderPresetAddon> addonPtr =
std::make_unique<CShaderPresetAddon>(shaderAddon);
if (addonPtr->CreateAddon())
{
for (const auto& extension : addonPtr->GetExtensions())
RegisterLoader(addonPtr.get(), extension);
m_shaderAddons.emplace(std::move(addonId), std::move(addonPtr));
}
else
{
m_shaderAddons.emplace(std::move(addonId), std::move(addonPtr));
}
}
}

View File

@ -0,0 +1,86 @@
/*
* Copyright (C) 2017-2025 Team Kodi
* This file is part of Kodi - https://kodi.tv
*
* SPDX-License-Identifier: GPL-2.0-or-later
* See LICENSES/README.md for more information.
*/
#pragma once
#include "IShaderPreset.h"
#include "addons/Addon.h"
#include <map>
#include <string>
namespace ADDON
{
struct AddonEvent;
class CAddonMgr;
class CBinaryAddonManager;
class CShaderPresetAddon;
} // namespace ADDON
namespace KODI
{
namespace SHADER
{
class IShaderPresetLoader;
class CShaderPresetFactory
{
public:
/*!
* \brief Create the factory and register all shader preset add-ons
*/
CShaderPresetFactory(ADDON::CAddonMgr& addons);
~CShaderPresetFactory();
/*!
* \brief Register an object that can load shader presets
*
* \param loader The instance of a preset loader
* \param extension The extension for which the loader can load presets
*/
void RegisterLoader(IShaderPresetLoader* loader, const std::string& extension);
/*!
* \brief Unregister the shader preset loader
*
* \param load The loader that was passed to RegisterLoader()
*/
void UnregisterLoader(IShaderPresetLoader* loader);
/*!
* \brief Load a preset from the given path
*
* \param presetPath The path to the shader preset
* \param[out] shaderPreset The loaded shader preset
*
* \return True if the preset was loaded, false otherwise
*/
bool LoadPreset(const std::string& presetPath, IShaderPreset& shaderPreset);
/*!
* \brief Check if a registered loader can load a given preset
*
* \param presetPath The path to the shader preset
*
* \return True if a loader can load the preset, false otherwise
*/
bool CanLoadPreset(const std::string& presetPath);
private:
void OnEvent(const ADDON::AddonEvent& event);
void UpdateAddons();
// Construction parameters
ADDON::CAddonMgr& m_addons;
std::map<std::string, IShaderPresetLoader*> m_loaders;
std::map<std::string, std::unique_ptr<ADDON::CShaderPresetAddon>> m_shaderAddons;
std::map<std::string, std::unique_ptr<ADDON::CShaderPresetAddon>> m_failedAddons;
};
} // namespace SHADER
} // namespace KODI

View File

@ -0,0 +1,124 @@
/*
* Copyright (C) 2017-2025 Team Kodi
* This file is part of Kodi - https://kodi.tv
*
* SPDX-License-Identifier: GPL-2.0-or-later
* See LICENSES/README.md for more information.
*/
#pragma once
#include <algorithm>
#include <map>
#include <memory>
#include <string>
#include <type_traits>
#include <vector>
namespace KODI
{
namespace SHADER
{
using ShaderParameterMap = std::map<std::string, float>;
enum class FilterType
{
NONE,
LINEAR,
NEAREST
};
enum class WrapType
{
BORDER,
EDGE,
REPEAT,
MIRRORED_REPEAT,
};
enum class ScaleType
{
INPUT,
ABSOLUTE_SCALE,
VIEWPORT,
};
struct FboScaleAxis
{
ScaleType scaleType{ScaleType::INPUT};
float scale{1.0};
unsigned int abs{1};
};
struct FboScale
{
bool sRgbFramebuffer{false};
bool floatFramebuffer{false};
FboScaleAxis scaleX;
FboScaleAxis scaleY;
};
struct ShaderLut
{
std::string strId;
std::string path;
FilterType filterType{FilterType::NONE};
WrapType wrapType{WrapType::BORDER};
bool mipmap{false};
};
struct ShaderParameter
{
std::string strId;
std::string description;
float current{0.0f};
float minimum{0.0f};
float initial{0.0f};
float maximum{0.0f};
float step{0.0f};
};
struct ShaderPass
{
std::string sourcePath;
std::string vertexSource;
std::string fragmentSource;
FilterType filterType{FilterType::NONE};
WrapType wrapType{WrapType::BORDER};
unsigned int frameCountMod{0};
FboScale fbo;
bool mipmap{false};
std::vector<ShaderLut> luts;
std::vector<ShaderParameter> parameters;
};
struct float2
{
float2() : x(0.0f), y(0.0f) {}
template<typename T>
float2(T x_, T y_) : x(static_cast<float>(x_)), y(static_cast<float>(y_))
{
static_assert(std::is_arithmetic<T>::value, "Not an arithmetic type");
}
bool operator==(const float2& rhs) const { return x == rhs.x && y == rhs.y; }
bool operator!=(const float2& rhs) const { return !(*this == rhs); }
template<typename T>
T Max()
{
return static_cast<T>(std::max(x, y));
}
template<typename T>
T Min()
{
return static_cast<T>(std::min(x, y));
}
float x;
float y;
};
} // namespace SHADER
} // namespace KODI

View File

@ -0,0 +1,52 @@
/*
* Copyright (C) 2017-2025 Team Kodi
* This file is part of Kodi - https://kodi.tv
*
* SPDX-License-Identifier: GPL-2.0-or-later
* See LICENSES/README.md for more information.
*/
#include "ShaderUtils.h"
using namespace KODI;
using namespace SHADER;
std::string CShaderUtils::StripParameterPragmas(std::string source)
{
size_t pragmaPosition;
size_t newlinePosition;
while ((pragmaPosition = source.find("#pragma parameter")) != std::string::npos)
{
newlinePosition = source.find_first_of("\n", pragmaPosition + 17);
if (newlinePosition != std::string::npos)
source.erase(pragmaPosition, newlinePosition - pragmaPosition + 1);
}
return source;
}
float2 CShaderUtils::GetOptimalTextureSize(float2 videoSize)
{
unsigned int textureWidth = 1;
unsigned int textureHeight = 1;
// Find smallest possible power-of-two sized width that can contain the input texture
while (true)
{
if (textureWidth >= videoSize.x)
break;
textureWidth *= 2;
}
// Find smallest possible power-of-two sized height that can contain the input texture
while (true)
{
if (textureHeight >= videoSize.y)
break;
textureHeight *= 2;
}
return float2(textureWidth, textureHeight);
}

View File

@ -0,0 +1,31 @@
/*
* Copyright (C) 2017-2025 Team Kodi
* This file is part of Kodi - https://kodi.tv
*
* SPDX-License-Identifier: GPL-2.0-or-later
* See LICENSES/README.md for more information.
*/
#pragma once
#include "ShaderTypes.h"
namespace KODI
{
namespace SHADER
{
class CShaderUtils
{
public:
/*!
* \brief Strip shader parameter pragmas from the source code
*/
static std::string StripParameterPragmas(std::string source);
/*!
* \brief Returns smallest possible power-of-two sized texture
*/
static float2 GetOptimalTextureSize(float2 videoSize);
};
} // namespace SHADER
} // namespace KODI

View File

@ -0,0 +1,20 @@
set(SOURCES RPWinOutputShader.cpp
ShaderDX.cpp
ShaderLutDX.cpp
ShaderPresetDX.cpp
ShaderSamplerDX.cpp
ShaderTextureDX.cpp
ShaderTextureDXRef.cpp
ShaderUtilsDX.cpp)
set(HEADERS RPWinOutputShader.h
ShaderDX.h
ShaderLutDX.h
ShaderPresetDX.h
ShaderSamplerDX.h
ShaderTextureDX.h
ShaderTextureDXRef.h
ShaderTypesDX.h
ShaderUtilsDX.h)
core_add_library(rp-shaders-windows)

View File

@ -0,0 +1,261 @@
/*
* Copyright (C) 2017-2025 Team Kodi
* This file is part of Kodi - https://kodi.tv
*
* SPDX-License-Identifier: GPL-2.0-or-later
* See LICENSES/README.md for more information.
*/
#include "RPWinOutputShader.h"
#include "ShaderTypesDX.h"
#include "filesystem/File.h"
#include "rendering/dx/DeviceResources.h"
#include "utils/log.h"
using namespace KODI;
using namespace SHADER;
bool CRPWinShader::CreateVertexBuffer(unsigned int count, unsigned int size)
{
if (!m_vb.Create(D3D11_BIND_VERTEX_BUFFER, count, size, DXGI_FORMAT_UNKNOWN, D3D11_USAGE_DYNAMIC))
return false;
uint16_t id[4] = {3, 0, 2, 1};
if (!m_ib.Create(D3D11_BIND_INDEX_BUFFER, ARRAYSIZE(id), sizeof(uint16_t), DXGI_FORMAT_R16_UINT,
D3D11_USAGE_IMMUTABLE, id))
return false;
m_vbsize = count * size;
m_vertsize = size;
return true;
}
bool CRPWinShader::CreateInputLayout(D3D11_INPUT_ELEMENT_DESC* layout, unsigned numElements)
{
D3DX11_PASS_DESC desc = {};
if (FAILED(m_effect.Get()->GetTechniqueByIndex(0)->GetPassByIndex(0)->GetDesc(&desc)))
{
CLog::LogF(LOGERROR, "Failed to get description");
return false;
}
Microsoft::WRL::ComPtr<ID3D11Device> pDevice = DX::DeviceResources::Get()->GetD3DDevice();
return SUCCEEDED(pDevice->CreateInputLayout(layout, numElements, desc.pIAInputSignature,
desc.IAInputSignatureSize, &m_inputLayout));
}
bool CRPWinShader::LockVertexBuffer(void** data)
{
if (!m_vb.Map(data))
{
CLog::LogF(LOGERROR, "Failed to lock vertex buffer");
return false;
}
return true;
}
bool CRPWinShader::UnlockVertexBuffer()
{
if (!m_vb.Unmap())
{
CLog::LogF(LOGERROR, "Failed to unlock vertex buffer");
return false;
}
return true;
}
bool CRPWinShader::LoadEffect(const std::string& filename, DefinesMap* defines)
{
CLog::LogF(LOGDEBUG, "Loading shader: {}", filename);
XFILE::CFileStream file;
if (!file.Open(filename))
{
CLog::LogF(LOGERROR, "Failed to open file: {}", filename);
return false;
}
std::string pStrEffect;
getline(file, pStrEffect, '\0');
if (!m_effect.Create(pStrEffect, defines))
{
CLog::LogF(LOGERROR, "{} failed", pStrEffect);
return false;
}
return true;
}
bool CRPWinShader::Execute(const std::vector<CD3DTexture*>& targets, unsigned int vertexIndexStep)
{
ID3D11DeviceContext* pContext = DX::DeviceResources::Get()->GetD3DContext();
Microsoft::WRL::ComPtr<ID3D11RenderTargetView> oldRT;
// The render target will be overridden: save the caller's original RT
if (!targets.empty())
pContext->OMGetRenderTargets(1, &oldRT, nullptr);
unsigned int cPasses;
if (!m_effect.Begin(&cPasses, 0))
{
CLog::LogF(LOGERROR, "Failed to begin D3D effect");
return false;
}
ID3D11Buffer* vertexBuffer = m_vb.Get();
ID3D11Buffer* indexBuffer = m_ib.Get();
unsigned int stride = m_vb.GetStride();
unsigned int offset = 0;
pContext->IASetVertexBuffers(0, 1, &vertexBuffer, &stride, &offset);
pContext->IASetIndexBuffer(indexBuffer, m_ib.GetFormat(), 0);
pContext->IASetInputLayout(m_inputLayout.Get());
pContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
for (unsigned int iPass = 0; iPass < cPasses; ++iPass)
{
SetTarget(targets.size() > iPass ? targets.at(iPass) : nullptr);
SetStepParams(iPass);
if (!m_effect.BeginPass(iPass))
{
CLog::LogF(LOGERROR, "Failed to begin D3D effect pass");
break;
}
pContext->DrawIndexed(4, 0, iPass * vertexIndexStep);
if (!m_effect.EndPass())
CLog::LogF(LOGERROR, "Failed to end D3D effect pass");
CD3DHelper::PSClearShaderResources(pContext);
}
if (!m_effect.End())
CLog::LogF(LOGERROR, "Failed to end D3D effect");
if (oldRT)
pContext->OMSetRenderTargets(1, oldRT.GetAddressOf(), nullptr);
return true;
}
void CRPWinShader::SetTarget(CD3DTexture* target)
{
m_target = target;
if (m_target)
{
DX::DeviceResources::Get()->GetD3DContext()->OMSetRenderTargets(1, target->GetAddressOfRTV(),
nullptr);
}
}
bool CRPWinOutputShader::Create(RETRO::SCALINGMETHOD scalingMethod)
{
CreateVertexBuffer(4, sizeof(CUSTOMVERTEX));
DefinesMap defines;
switch (scalingMethod)
{
case RETRO::SCALINGMETHOD::NEAREST:
defines["SAMP_NEAREST"] = "";
break;
case RETRO::SCALINGMETHOD::LINEAR:
default:
break;
}
const std::string effectPath("special://xbmc/system/shaders/rp_output_d3d.fx");
if (!LoadEffect(effectPath, &defines))
{
CLog::LogF(LOGERROR, "Failed to load shader: {}", effectPath);
return false;
}
// Create input layout
D3D11_INPUT_ELEMENT_DESC layout[] = {
{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
{"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0},
};
return CreateInputLayout(layout, ARRAYSIZE(layout));
}
void CRPWinOutputShader::Render(CD3DTexture& sourceTexture,
CRect sourceRect,
const CPoint points[4],
CRect& viewPort,
CD3DTexture& target,
unsigned int range /* = 0 */)
{
PrepareParameters(sourceTexture.GetWidth(), sourceTexture.GetHeight(), sourceRect, points);
SetShaderParameters(sourceTexture, range, viewPort);
Execute({&target}, 4);
}
void CRPWinOutputShader::PrepareParameters(unsigned int sourceWidth,
unsigned int sourceHeight,
CRect sourceRect,
const CPoint points[4])
{
bool changed = false;
for (unsigned int i = 0; i < 4 && !changed; ++i)
changed = points[i] != m_destPoints[i];
if (m_sourceWidth != sourceWidth || m_sourceHeight != sourceHeight ||
m_sourceRect != sourceRect || changed)
{
m_sourceWidth = sourceWidth;
m_sourceHeight = sourceHeight;
m_sourceRect = sourceRect;
for (unsigned int i = 0; i < 4; ++i)
m_destPoints[i] = points[i];
CUSTOMVERTEX* v = nullptr;
LockVertexBuffer(static_cast<void**>(static_cast<void*>(&v)));
v[0].x = m_destPoints[0].x;
v[0].y = m_destPoints[0].y;
v[0].z = 0.0f;
v[0].tu = m_sourceRect.x1 / m_sourceWidth;
v[0].tv = m_sourceRect.y1 / m_sourceHeight;
v[1].x = m_destPoints[1].x;
v[1].y = m_destPoints[1].y;
v[1].z = 0.0f;
v[1].tu = m_sourceRect.x2 / m_sourceWidth;
v[1].tv = m_sourceRect.y1 / m_sourceHeight;
v[2].x = m_destPoints[2].x;
v[2].y = m_destPoints[2].y;
v[2].z = 0.0f;
v[2].tu = m_sourceRect.x2 / m_sourceWidth;
v[2].tv = m_sourceRect.y2 / m_sourceHeight;
v[3].x = m_destPoints[3].x;
v[3].y = m_destPoints[3].y;
v[3].z = 0.0f;
v[3].tu = m_sourceRect.x1 / m_sourceWidth;
v[3].tv = m_sourceRect.y2 / m_sourceHeight;
UnlockVertexBuffer();
}
}
void CRPWinOutputShader::SetShaderParameters(CD3DTexture& sourceTexture,
unsigned int range,
CRect& viewPort)
{
m_effect.SetTechnique("OUTPUT_T");
m_effect.SetResources("g_Texture", sourceTexture.GetAddressOfSRV(), 1);
const float viewPortArray[2] = {viewPort.Width(), viewPort.Height()};
m_effect.SetFloatArray("g_viewPort", viewPortArray, 2);
const float params[3] = {static_cast<float>(range)};
m_effect.SetFloatArray("m_params", params, 1);
}

View File

@ -0,0 +1,86 @@
/*
* Copyright (C) 2017-2025 Team Kodi
* This file is part of Kodi - https://kodi.tv
*
* SPDX-License-Identifier: GPL-2.0-or-later
* See LICENSES/README.md for more information.
*/
#pragma once
#include "cores/GameSettings.h"
#include "guilib/D3DResource.h"
#include "utils/Geometry.h"
#include <memory>
#include <string>
#include <vector>
#include <d3d11.h>
#include <wrl/client.h>
namespace KODI
{
namespace SHADER
{
class CRPWinShader
{
public:
virtual ~CRPWinShader() = default;
protected:
virtual bool CreateVertexBuffer(unsigned int vertCount, unsigned int vertSize);
virtual bool CreateInputLayout(D3D11_INPUT_ELEMENT_DESC* layout, unsigned numElements);
virtual bool LockVertexBuffer(void** data);
virtual bool UnlockVertexBuffer();
virtual bool LoadEffect(const std::string& filename, DefinesMap* defines);
virtual bool Execute(const std::vector<CD3DTexture*>& targets, unsigned int vertexIndexStep);
virtual void SetStepParams(unsigned stepIndex) {}
CD3DEffect m_effect;
CD3DTexture* m_target{nullptr};
private:
void SetTarget(CD3DTexture* target);
CD3DBuffer m_vb;
CD3DBuffer m_ib;
unsigned int m_vbsize{0};
unsigned int m_vertsize{0};
Microsoft::WRL::ComPtr<ID3D11InputLayout> m_inputLayout;
};
class CRPWinOutputShader : protected CRPWinShader
{
public:
~CRPWinOutputShader() override = default;
bool Create(RETRO::SCALINGMETHOD scalingMethod);
void Render(CD3DTexture& sourceTexture,
CRect sourceRect,
const CPoint points[4],
CRect& viewPort,
CD3DTexture& target,
unsigned int range = 0);
private:
void PrepareParameters(unsigned int sourceWidth,
unsigned int sourceHeight,
CRect sourceRect,
const CPoint points[4]);
void SetShaderParameters(CD3DTexture& sourceTexture, unsigned int range, CRect& viewPort);
unsigned int m_sourceWidth{0};
unsigned int m_sourceHeight{0};
CRect m_sourceRect{0.f, 0.f, 0.f, 0.f};
CPoint m_destPoints[4] = {
{0.f, 0.f},
{0.f, 0.f},
{0.f, 0.f},
{0.f, 0.f},
};
};
} // namespace SHADER
} // namespace KODI

View File

@ -0,0 +1,274 @@
/*
* Copyright (C) 2017-2025 Team Kodi
* This file is part of Kodi - https://kodi.tv
*
* SPDX-License-Identifier: GPL-2.0-or-later
* See LICENSES/README.md for more information.
*/
#include "ShaderDX.h"
#include "ShaderTextureDX.h"
#include "ShaderTextureDXRef.h"
#include "ShaderTypesDX.h"
#include "ShaderUtilsDX.h"
#include "application/Application.h"
#include "cores/RetroPlayer/rendering/RenderContext.h"
#include "cores/RetroPlayer/shaders/IShaderLut.h"
#include "cores/RetroPlayer/shaders/ShaderUtils.h"
#include "guilib/TextureDX.h"
#include "rendering/dx/RenderSystemDX.h"
#include "utils/URIUtils.h"
#include "utils/log.h"
using namespace KODI;
using namespace SHADER;
CShaderDX::CShaderDX(RETRO::CRenderContext& context) : m_context(context)
{
}
CShaderDX::~CShaderDX()
{
if (m_pInputBuffer != nullptr)
m_pInputBuffer->Release();
}
bool CShaderDX::Create(std::string shaderSource,
std::string shaderPath,
ShaderParameterMap shaderParameters,
std::vector<std::shared_ptr<IShaderLut>> luts,
float2 viewPortSize,
unsigned int passIdx,
unsigned int frameCountMod)
{
if (shaderPath.empty())
{
CLog::Log(LOGERROR, "CShaderDX::Create: Can't load empty shader path");
return false;
}
m_shaderSource = std::move(shaderSource);
m_shaderPath = std::move(shaderPath);
m_shaderParameters = std::move(shaderParameters);
m_luts = std::move(luts);
m_viewportSize = viewPortSize;
m_passIdx = passIdx;
m_frameCountMod = frameCountMod;
//m_pSampler = reinterpret_cast<ID3D11SamplerState*>(sampler);
DefinesMap defines;
defines["HLSL_4"] = ""; // Using Shader Model 4
defines["HLSL_FX"] = ""; // And the FX11 framework
// We implement runtime shader parameters ("#pragma parameter")
// @note Runtime shader parameters allow convenient experimentation with real-time
// feedback, as well as override-ability by presets, but sometimes they are
// much slower because they prevent static evaluation of a lot of math.
// Disabling them drastically speeds up shaders that use them heavily.
defines["PARAMETER_UNIFORM"] = "";
m_effect.AddIncludePath(URIUtils::GetBasePath(m_shaderPath));
if (!m_effect.Create(m_shaderSource, &defines))
{
CLog::Log(LOGERROR, "CShaderDX::Create: Failed to load video shader: {}", m_shaderPath);
return false;
}
return true;
}
void CShaderDX::Render(IShaderTexture& source, IShaderTexture& target)
{
CShaderTextureDX& sourceDX = static_cast<CShaderTextureDX&>(source);
// Get source texture object
const CD3DTexture& sourceTexture = sourceDX.GetTexture();
//! @todo Handle ref textures better
CShaderTextureDX* targetDX = dynamic_cast<CShaderTextureDX*>(&target);
CShaderTextureDXRef* targetDXRef = dynamic_cast<CShaderTextureDXRef*>(&target);
CD3DTexture& targetTexture =
targetDX != nullptr ? targetDX->GetTexture() : targetDXRef->GetTexture();
//! @todo Doesn't work. Another PSSetSamplers gets called by FX11 right before rendering
// overriding this.
/*
CRenderSystemDX *renderingDX = static_cast<CRenderSystemDX*>(m_context.Rendering());
renderingDX->Get3D11Context()->PSSetSamplers(2, 1, &m_pSampler);
*/
//! @todo Check for nullptr
SetShaderParameters(sourceTexture);
Execute({&targetTexture}, 4);
}
void CShaderDX::SetSizes(const float2& prevSize,
const float2& prevTextureSize,
const float2& nextSize)
{
m_inputSize = prevSize;
m_inputTextureSize = prevTextureSize;
m_outputSize = nextSize;
}
void CShaderDX::PrepareParameters(
CPoint dest[4],
IShaderTexture& sourceTexture,
const std::vector<std::unique_ptr<IShaderTexture>>& pShaderTextures,
const std::vector<std::unique_ptr<IShader>>& pShaders,
uint64_t frameCount)
{
CUSTOMVERTEX* v;
LockVertexBuffer(reinterpret_cast<void**>(&v));
if (m_passIdx + 1 != static_cast<unsigned int>(pShaders.size())) // Not last pass
{
// top left
v[0].x = -m_outputSize.x / 2;
v[0].y = -m_outputSize.y / 2;
// top right
v[1].x = m_outputSize.x / 2;
v[1].y = -m_outputSize.y / 2;
// bottom right
v[2].x = m_outputSize.x / 2;
v[2].y = m_outputSize.y / 2;
// bottom left
v[3].x = -m_outputSize.x / 2;
v[3].y = m_outputSize.y / 2;
// Set destination rectangle size
m_destSize = m_outputSize;
}
else // Last pass
{
// top left
v[0].x = dest[0].x - m_outputSize.x / 2;
v[0].y = dest[0].y - m_outputSize.y / 2;
// top right
v[1].x = dest[1].x - m_outputSize.x / 2;
v[1].y = dest[1].y - m_outputSize.y / 2;
// bottom right
v[2].x = dest[2].x - m_outputSize.x / 2;
v[2].y = dest[2].y - m_outputSize.y / 2;
// bottom left
v[3].x = dest[3].x - m_outputSize.x / 2;
v[3].y = dest[3].y - m_outputSize.y / 2;
// Set destination rectangle size for the last pass
m_destSize = {dest[2].x - dest[0].x, dest[2].y - dest[0].y};
}
// top left
v[0].z = 0;
v[0].tu = 0;
v[0].tv = 0;
// top right
v[1].z = 0;
v[1].tu = 1;
v[1].tv = 0;
// bottom right
v[2].z = 0;
v[2].tu = 1;
v[2].tv = 1;
// bottom left
v[3].z = 0;
v[3].tu = 0;
v[3].tv = 1;
UnlockVertexBuffer();
UpdateInputBuffer(frameCount);
}
void CShaderDX::UpdateMVP()
{
const float xScale = 1.0f / m_outputSize.x * 2.0f;
const float yScale = -1.0f / m_outputSize.y * 2.0f;
// Update projection matrix
m_MVP = DirectX::XMFLOAT4X4(xScale, 0, 0, 0, 0, yScale, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
}
bool CShaderDX::CreateVertexBuffer(unsigned int vertCount, unsigned int vertSize)
{
return CRPWinShader::CreateVertexBuffer(vertCount, vertSize);
}
bool CShaderDX::CreateInputLayout(D3D11_INPUT_ELEMENT_DESC* layout, unsigned int numElements)
{
return CRPWinShader::CreateInputLayout(layout, numElements);
}
bool CShaderDX::CreateInputBuffer()
{
ID3D11Device1* pDevice = DX::DeviceResources::Get()->GetD3DDevice();
cbInput inputInitData = GetInputData();
UINT inputBufSize = static_cast<UINT>((sizeof(cbInput) + 15) & ~15);
CD3D11_BUFFER_DESC cbInputDesc(inputBufSize, D3D11_BIND_CONSTANT_BUFFER, D3D11_USAGE_DYNAMIC,
D3D11_CPU_ACCESS_WRITE);
D3D11_SUBRESOURCE_DATA initInputSubresource = {&inputInitData, 0, 0};
if (FAILED(pDevice->CreateBuffer(&cbInputDesc, &initInputSubresource, &m_pInputBuffer)))
{
CLog::Log(LOGERROR, "CShaderDX::CreateInputBuffer: Failed to create constant buffer for video "
"shader input data");
return false;
}
return true;
}
void CShaderDX::UpdateInputBuffer(uint64_t frameCount)
{
ID3D11DeviceContext1* pContext = DX::DeviceResources::Get()->GetD3DContext();
cbInput input = GetInputData(frameCount);
cbInput* pData;
void** ppData = reinterpret_cast<void**>(&pData);
D3D11_MAPPED_SUBRESOURCE resource;
if (SUCCEEDED(pContext->Map(m_pInputBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &resource)))
{
*ppData = resource.pData;
memcpy(*ppData, &input, sizeof(cbInput));
pContext->Unmap(m_pInputBuffer, 0);
}
}
CShaderDX::cbInput CShaderDX::GetInputData(uint64_t frameCount) const
{
if (m_frameCountMod != 0)
frameCount %= m_frameCountMod;
cbInput input = {
{CShaderUtilsDX::ToDXVector(m_inputSize)}, // video_size
{CShaderUtilsDX::ToDXVector(m_inputTextureSize)}, // texture_size
{CShaderUtilsDX::ToDXVector(m_destSize)}, // output_size
// Current frame count that can be modulo'ed
static_cast<float>(frameCount), // frame_count
// Time always flows forward
1.0f // frame_direction
};
return input;
}
void CShaderDX::SetShaderParameters(const CD3DTexture& sourceTexture)
{
m_effect.SetTechnique("TEQ");
m_effect.SetResources("decal", {const_cast<CD3DTexture&>(sourceTexture).GetAddressOfSRV()}, 1);
m_effect.SetMatrix("modelViewProj", reinterpret_cast<const float*>(&m_MVP));
//! @todo(optimization) Add frame_count to separate cbuffer
m_effect.SetConstantBuffer("input", m_pInputBuffer);
for (const auto& paramIt : m_shaderParameters)
m_effect.SetFloatArray(paramIt.first.c_str(), &paramIt.second, 1);
for (const std::shared_ptr<IShaderLut>& lut : m_luts)
{
CDXTexture* texture = dynamic_cast<CDXTexture*>(lut->GetTexture());
if (texture != nullptr)
m_effect.SetTexture(lut->GetID().c_str(), texture->GetShaderResource());
}
}

View File

@ -0,0 +1,148 @@
/*
* Copyright (C) 2017-2025 Team Kodi
* This file is part of Kodi - https://kodi.tv
*
* SPDX-License-Identifier: GPL-2.0-or-later
* See LICENSES/README.md for more information.
*/
#pragma once
#include "ShaderTextureDX.h"
#include "ShaderTypesDX.h"
#include "cores/RetroPlayer/shaders/IShader.h"
#include "cores/RetroPlayer/shaders/windows/RPWinOutputShader.h"
#include "guilib/D3DResource.h"
#include <stdint.h>
#include <DirectXMath.h>
namespace KODI
{
namespace RETRO
{
class CRenderContext;
}
namespace SHADER
{
class IShaderLut;
class IShaderSampler;
class CShaderDX : protected CRPWinShader, public IShader
{
public:
CShaderDX(RETRO::CRenderContext& context);
~CShaderDX() override;
// Implementation of IShader
bool Create(std::string shaderSource,
std::string shaderPath,
ShaderParameterMap shaderParameters,
std::vector<std::shared_ptr<IShaderLut>> luts,
float2 viewPortSize,
unsigned int passIdx,
unsigned int frameCountMod = 0) override;
void Render(IShaderTexture& source, IShaderTexture& target) override;
void SetSizes(const float2& prevSize,
const float2& prevTextureSize,
const float2& nextSize) override;
void PrepareParameters(CPoint dest[4],
IShaderTexture& sourceTexture,
const std::vector<std::unique_ptr<IShaderTexture>>& pShaderTextures,
const std::vector<std::unique_ptr<IShader>>& pShaders,
uint64_t frameCount) override;
void UpdateMVP() override;
/*!
* \brief Construct the vertex buffer that will be used to render the shader
*
* \param vertCount Number of vertices to construct. Commonly 4, for rectangular screens
* \param vertSize Size of each vertex's data in bytes
*
* \return False if creating the vertex buffer failed, true otherwise
*/
bool CreateVertexBuffer(unsigned int vertCount, unsigned int vertSize);
/*!
* \brief Creates the data layout of the input-assembler stage
*
* \param layout Description of the inputs to the vertex shader
* \param numElements Number of inputs to the vertex shader
*
* \return False if creating the input layout failed, true otherwise.
*/
bool CreateInputLayout(D3D11_INPUT_ELEMENT_DESC* layout, unsigned int numElements);
/*!
* \brief Creates the buffer that will be used to send "input" (as per the
* spec) data to the shader
*
* \return False if creating the input buffer failed, true otherwise
*/
bool CreateInputBuffer();
private:
struct cbInput
{
DirectX::XMFLOAT2 video_size;
DirectX::XMFLOAT2 texture_size;
DirectX::XMFLOAT2 output_size;
float frame_count;
float frame_direction;
};
void UpdateInputBuffer(uint64_t frameCount);
cbInput GetInputData(uint64_t frameCount = 0) const;
void SetShaderParameters(const CD3DTexture& sourceTexture);
// Construction parameters
RETRO::CRenderContext& m_context;
// Currently loaded shader's source code
std::string m_shaderSource;
// Currently loaded shader's relative path
std::string m_shaderPath;
// Struct with all parameters pertaining to the shader
ShaderParameterMap m_shaderParameters;
// Look-up textures pertaining to the shader
std::vector<std::shared_ptr<IShaderLut>> m_luts; //! @todo Back to DX maybe
// Resolution of the input of the shader
float2 m_inputSize;
// Resolution of the texture that holds the input
float2 m_inputTextureSize;
// Resolution of the output viewport of the shader
float2 m_outputSize;
// Resolution of the destination rectangle of the shader
float2 m_destSize;
// Resolution of the viewport/window
float2 m_viewportSize;
// Projection matrix
DirectX::XMFLOAT4X4 m_MVP{};
// Index of the video shader pass
unsigned int m_passIdx{0};
// Value to modulo (%) frame count with
// Unused if 0
unsigned int m_frameCountMod{0};
// Holds the data bound to the input cbuffer (cbInput here)
ID3D11Buffer* m_pInputBuffer{nullptr};
// Sampler state
//ID3D11SamplerState* m_pSampler{nullptr};
};
} // namespace SHADER
} // namespace KODI

View File

@ -0,0 +1,65 @@
/*
* Copyright (C) 2017-2025 Team Kodi
* This file is part of Kodi - https://kodi.tv
*
* SPDX-License-Identifier: GPL-2.0-or-later
* See LICENSES/README.md for more information.
*/
#include "ShaderLutDX.h"
#include "ShaderUtilsDX.h"
#include "cores/RetroPlayer/rendering/RenderContext.h"
#include "cores/RetroPlayer/shaders/IShaderPreset.h"
#include "guilib/TextureDX.h"
#include "rendering/dx/RenderSystemDX.h"
#include "utils/log.h"
#include <utility>
using namespace KODI;
using namespace SHADER;
CShaderLutDX::CShaderLutDX(std::string id, std::string path)
: IShaderLut(std::move(id), std::move(path))
{
}
CShaderLutDX::~CShaderLutDX() = default;
bool CShaderLutDX::Create(RETRO::CRenderContext& context, const ShaderLut& lut)
{
std::unique_ptr<CTexture> lutTexture{CreateLUTexture(lut)};
if (!lutTexture)
{
CLog::LogF(LOGWARNING, "CShaderLutDX::Create: Couldn't create texture for LUT: {}", lut.strId);
return false;
}
m_texture = std::move(lutTexture);
return true;
}
std::unique_ptr<CTexture> CShaderLutDX::CreateLUTexture(const ShaderLut& lut)
{
std::unique_ptr<CTexture> texture = CTexture::LoadFromFile(lut.path);
CDXTexture* textureDX = static_cast<CDXTexture*>(texture.get());
if (textureDX == nullptr)
{
CLog::Log(LOGERROR, "CShaderLutDX::CreateLUTexture: Couldn't open LUT: {}", lut.path);
return std::unique_ptr<CTexture>();
}
if (lut.mipmap)
textureDX->SetMipmapping();
textureDX->SetScalingMethod(lut.filterType == FilterType::LINEAR ? TEXTURE_SCALING::LINEAR
: TEXTURE_SCALING::NEAREST);
textureDX->LoadToGPU();
//! @todo Set LUT wrap type
//D3D11_TEXTURE_ADDRESS_MODE wrapType = CShaderUtilsDX::TranslateWrapType(lut.wrapType);
return texture;
}

View File

@ -0,0 +1,51 @@
/*
* Copyright (C) 2017-2025 Team Kodi
* This file is part of Kodi - https://kodi.tv
*
* SPDX-License-Identifier: GPL-2.0-or-later
* See LICENSES/README.md for more information.
*/
#pragma once
#include "cores/RetroPlayer/shaders/IShaderLut.h"
#include "cores/RetroPlayer/shaders/ShaderTypes.h"
#include <memory>
#include <string>
#include <d3d11.h>
namespace KODI
{
namespace RETRO
{
class CRenderContext;
}
namespace SHADER
{
class CTextureBase;
struct ShaderLut;
class CShaderLutDX : public IShaderLut
{
public:
CShaderLutDX() = default;
CShaderLutDX(std::string id, std::string path);
// Destructor
~CShaderLutDX() override;
// Implementation of IShaderLut
bool Create(RETRO::CRenderContext& context, const ShaderLut& lut) override;
CTexture* GetTexture() override { return m_texture.get(); }
private:
static std::unique_ptr<CTexture> CreateLUTexture(const ShaderLut& lut);
std::unique_ptr<CTexture> m_texture;
};
} // namespace SHADER
} // namespace KODI

View File

@ -0,0 +1,268 @@
/*
* Copyright (C) 2017-2025 Team Kodi
* This file is part of Kodi - https://kodi.tv
*
* SPDX-License-Identifier: GPL-2.0-or-later
* See LICENSES/README.md for more information.
*/
#include "ShaderPresetDX.h"
#include "cores/RetroPlayer/rendering/RenderContext.h"
#include "cores/RetroPlayer/shaders/windows/ShaderDX.h"
#include "cores/RetroPlayer/shaders/windows/ShaderLutDX.h"
#include "cores/RetroPlayer/shaders/windows/ShaderTextureDX.h"
#include "cores/RetroPlayer/shaders/windows/ShaderTypesDX.h"
#include "rendering/dx/RenderSystemDX.h"
#include "utils/log.h"
#include <regex>
using namespace KODI;
using namespace SHADER;
CShaderPresetDX::CShaderPresetDX(RETRO::CRenderContext& context,
unsigned videoWidth,
unsigned videoHeight)
: CShaderPreset(context, videoWidth, videoHeight)
{
}
bool CShaderPresetDX::CreateShaders()
{
const unsigned int numPasses = static_cast<unsigned int>(m_passes.size());
//! @todo Is this pass specific?
for (unsigned int shaderIdx = 0; shaderIdx < numPasses; ++shaderIdx)
{
std::vector<std::shared_ptr<IShaderLut>> passLUTsDX;
const ShaderPass& pass = m_passes[shaderIdx];
const unsigned int numPassLuts = static_cast<unsigned int>(pass.luts.size());
for (unsigned int i = 0; i < numPassLuts; ++i)
{
const ShaderLut& lutStruct = pass.luts[i];
std::shared_ptr<CShaderLutDX> passLut =
std::make_shared<CShaderLutDX>(lutStruct.strId, lutStruct.path);
if (passLut->Create(m_context, lutStruct))
passLUTsDX.emplace_back(std::move(passLut));
}
// Create the shader
std::unique_ptr<CShaderDX> videoShader = std::make_unique<CShaderDX>(m_context);
const std::string& shaderSource = pass.vertexSource; // Also contains fragment source
const std::string& shaderPath = pass.sourcePath;
// Get only the parameters belonging to this specific shader
ShaderParameterMap passParameters = GetShaderParameters(pass.parameters, pass.vertexSource);
if (!videoShader->Create(shaderSource, shaderPath, std::move(passParameters),
std::move(passLUTsDX), m_outputSize, shaderIdx, pass.frameCountMod))
{
CLog::Log(LOGERROR, "CShaderPresetDX::CreateShaders: Couldn't create a video shader");
return false;
}
m_pShaders.push_back(std::move(videoShader));
/*
IShaderSampler* passSampler = reinterpret_cast<IShaderSampler*>(
pass.filter == FILTER_TYPE_LINEAR
? m_pSampLinear
: m_pSampNearest); //! @todo Wrap in CShaderSamplerDX instead of reinterpret_cast
//! @todo Set passSampler to m_pSampler in the videoShader
videoShader->SetSampler(passSampler);
*/
}
return true;
}
bool CShaderPresetDX::CreateLayouts()
{
for (std::unique_ptr<IShader>& videoShader : m_pShaders)
{
CShaderDX* videoShaderDX = static_cast<CShaderDX*>(videoShader.get());
videoShaderDX->CreateVertexBuffer(4, sizeof(CUSTOMVERTEX));
// Create input layout
D3D11_INPUT_ELEMENT_DESC layout[] = {
{"SV_POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
{"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0},
{"TEXCOORD", 1, DXGI_FORMAT_R32G32_FLOAT, 0, 20, D3D11_INPUT_PER_VERTEX_DATA, 0}};
if (!videoShaderDX->CreateInputLayout(layout, ARRAYSIZE(layout)))
{
CLog::Log(
LOGERROR,
"CShaderPresetDX::CreateLayouts: Failed to create input layout for Input Assembler");
return false;
}
}
return true;
}
bool CShaderPresetDX::CreateBuffers()
{
for (std::unique_ptr<IShader>& videoShader : m_pShaders)
{
CShaderDX* videoShaderDX = static_cast<CShaderDX*>(videoShader.get());
videoShaderDX->CreateInputBuffer();
}
return true;
}
bool CShaderPresetDX::CreateShaderTextures()
{
m_pShaderTextures.clear();
float2 prevSize = m_videoSize;
float2 prevTextureSize = m_videoSize;
const unsigned int numPasses = static_cast<unsigned int>(m_passes.size());
for (unsigned int shaderIdx = 0; shaderIdx < numPasses; ++shaderIdx)
{
const ShaderPass& pass = m_passes[shaderIdx];
// Resolve final texture resolution, taking scale type and scale multiplier into account
float2 scaledSize;
float2 textureSize;
switch (pass.fbo.scaleX.scaleType)
{
case ScaleType::ABSOLUTE_SCALE:
scaledSize.x = static_cast<float>(pass.fbo.scaleX.abs);
break;
case ScaleType::VIEWPORT:
scaledSize.x =
pass.fbo.scaleX.scale ? pass.fbo.scaleX.scale * m_outputSize.x : m_outputSize.x;
break;
case ScaleType::INPUT:
default:
scaledSize.x = pass.fbo.scaleX.scale ? pass.fbo.scaleX.scale * prevSize.x : prevSize.x;
break;
}
switch (pass.fbo.scaleY.scaleType)
{
case ScaleType::ABSOLUTE_SCALE:
scaledSize.y = static_cast<float>(pass.fbo.scaleY.abs);
break;
case ScaleType::VIEWPORT:
scaledSize.y =
pass.fbo.scaleY.scale ? pass.fbo.scaleY.scale * m_outputSize.y : m_outputSize.y;
break;
case ScaleType::INPUT:
default:
scaledSize.y = pass.fbo.scaleY.scale ? pass.fbo.scaleY.scale * prevSize.y : prevSize.y;
break;
}
if (shaderIdx + 1 == numPasses)
{
// We're supposed to output at full (viewport) resolution
scaledSize.x = m_outputSize.x;
scaledSize.y = m_outputSize.y;
}
else
{
// Determine the framebuffer data format
DXGI_FORMAT textureFormat;
if (pass.fbo.floatFramebuffer)
{
// Give priority to float framebuffer parameter (we can't use both float and sRGB)
textureFormat = DXGI_FORMAT_R32G32B32A32_FLOAT;
}
else
{
if (pass.fbo.sRgbFramebuffer)
textureFormat = DXGI_FORMAT_B8G8R8A8_UNORM_SRGB;
else
textureFormat = DXGI_FORMAT_B8G8R8A8_UNORM;
}
//! @todo Enable usage of optimal texture sizes once multi-pass preset
// geometry and LUT rendering are fixed.
//
// Current issues:
// - Enabling optimal texture sizes breaks geometry for many multi-pass
// presets
// - LUTs render incorrectly due to missing per-pass and per-LUT
// TexCoord attributes.
//
// Planned solution:
// - Implement additional TexCoord attributes for each pass and LUT,
// setting coordinates to `xamt` and `yamt` instead of 1
//
// Reference implementation in RetroArch:
// https://github.com/libretro/RetroArch/blob/09a59edd6b415b7bd124b03bda68ccc4d60b0ea8/gfx/drivers/gl2.c#L3018
//
textureSize = scaledSize; // CShaderUtils::GetOptimalTextureSize(scaledSize)
std::shared_ptr<CD3DTexture> textureDX = std::make_shared<CD3DTexture>();
if (!textureDX->Create(static_cast<UINT>(textureSize.x), static_cast<UINT>(textureSize.y), 1,
D3D11_USAGE_DEFAULT, textureFormat, nullptr, 0))
{
CLog::Log(
LOGERROR,
"CShaderPresetDX::CreateShaderTextures: Couldn't create texture for video shader: {}",
pass.sourcePath);
return false;
}
m_pShaderTextures.emplace_back(std::make_unique<CShaderTextureDX>(std::move(textureDX)));
}
// Notify shader of its source and dest size
m_pShaders[shaderIdx]->SetSizes(prevSize, prevTextureSize, scaledSize);
prevSize = scaledSize;
prevTextureSize = textureSize;
}
UpdateMVPs();
return true;
}
bool CShaderPresetDX::CreateSamplers()
{
// Describe the Sampler States
// As specified in the common-shaders spec
D3D11_SAMPLER_DESC sampDesc;
ZeroMemory(&sampDesc, sizeof(D3D11_SAMPLER_DESC));
sampDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT;
sampDesc.AddressU = D3D11_TEXTURE_ADDRESS_BORDER;
sampDesc.AddressV = D3D11_TEXTURE_ADDRESS_BORDER;
sampDesc.AddressW = D3D11_TEXTURE_ADDRESS_BORDER;
sampDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
sampDesc.MinLOD = 0;
sampDesc.MaxLOD = D3D11_FLOAT32_MAX;
FLOAT blackBorder[4] = {1, 0, 0, 1}; //! @todo Turn this back to black
memcpy(sampDesc.BorderColor, &blackBorder, 4 * sizeof(FLOAT));
ID3D11Device1* pDevice = DX::DeviceResources::Get()->GetD3DDevice();
if (FAILED(pDevice->CreateSamplerState(&sampDesc, &m_pSampNearest)))
return false;
D3D11_SAMPLER_DESC sampDescLinear = sampDesc;
sampDescLinear.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
if (FAILED(pDevice->CreateSamplerState(&sampDescLinear, &m_pSampLinear)))
return false;
return true;
}
void CShaderPresetDX::RenderShader(IShader& shader, IShaderTexture& source, IShaderTexture& target)
{
const CRect newViewPort(0.f, 0.f, target.GetWidth(), target.GetHeight());
m_context.SetViewPort(newViewPort);
m_context.SetScissors(newViewPort);
shader.Render(source, target);
}

View File

@ -0,0 +1,54 @@
/*
* Copyright (C) 2017-2025 Team Kodi
* This file is part of Kodi - https://kodi.tv
*
* SPDX-License-Identifier: GPL-2.0-or-later
* See LICENSES/README.md for more information.
*/
#pragma once
#include "cores/RetroPlayer/shaders/ShaderPreset.h"
#include <d3d11.h>
namespace KODI
{
namespace RETRO
{
class CRenderContext;
}
namespace SHADER
{
class IShader;
class IShaderTexture;
class CShaderPresetDX : public CShaderPreset
{
public:
// Instance of CShaderPreset
explicit CShaderPresetDX(RETRO::CRenderContext& context,
unsigned videoWidth = 0,
unsigned videoHeight = 0);
~CShaderPresetDX() override = default;
protected:
// Implementation of CShaderPreset
bool CreateShaders() override;
bool CreateLayouts() override;
bool CreateBuffers() override;
bool CreateShaderTextures() override;
bool CreateSamplers() override;
void RenderShader(IShader& shader, IShaderTexture& source, IShaderTexture& target) override;
private:
// Point/nearest neighbor sampler
ID3D11SamplerState* m_pSampNearest = nullptr;
// Linear sampler
ID3D11SamplerState* m_pSampLinear = nullptr;
};
} // namespace SHADER
} // namespace KODI

View File

@ -0,0 +1,22 @@
/*
* Copyright (C) 2017-2025 Team Kodi
* This file is part of Kodi - https://kodi.tv
*
* SPDX-License-Identifier: GPL-2.0-or-later
* See LICENSES/README.md for more information.
*/
#include "ShaderSamplerDX.h"
using namespace KODI;
using namespace SHADER;
CShaderSamplerDX::CShaderSamplerDX(ID3D11SamplerState* sampler) : m_sampler(sampler)
{
}
CShaderSamplerDX::~CShaderSamplerDX()
{
if (m_sampler != nullptr)
m_sampler->Release();
}

View File

@ -0,0 +1,31 @@
/*
* Copyright (C) 2017-2025 Team Kodi
* This file is part of Kodi - https://kodi.tv
*
* SPDX-License-Identifier: GPL-2.0-or-later
* See LICENSES/README.md for more information.
*/
#pragma once
#include "cores/RetroPlayer/shaders/IShaderSampler.h"
#include <d3d11.h>
namespace KODI
{
namespace SHADER
{
class CShaderSamplerDX : public IShaderSampler
{
public:
CShaderSamplerDX(ID3D11SamplerState* sampler);
~CShaderSamplerDX() override;
private:
ID3D11SamplerState* const m_sampler;
};
} // namespace SHADER
} // namespace KODI

View File

@ -0,0 +1,38 @@
/*
* Copyright (C) 2019-2025 Team Kodi
* This file is part of Kodi - https://kodi.tv
*
* SPDX-License-Identifier: GPL-2.0-or-later
* See LICENSES/README.md for more information.
*/
#include "ShaderTextureDX.h"
#include "guilib/D3DResource.h"
#include <cassert>
#include <utility>
using namespace KODI;
using namespace SHADER;
CShaderTextureDX::CShaderTextureDX(std::shared_ptr<CD3DTexture> texture)
: m_texture(std::move(texture))
{
assert(m_texture.get() != nullptr);
}
float CShaderTextureDX::GetWidth() const
{
return static_cast<float>(m_texture->GetWidth());
}
float CShaderTextureDX::GetHeight() const
{
return static_cast<float>(m_texture->GetHeight());
}
ID3D11ShaderResourceView* CShaderTextureDX::GetShaderResource() const
{
return m_texture->GetShaderResource();
}

View File

@ -0,0 +1,48 @@
/*
* Copyright (C) 2017-2025 Team Kodi
* This file is part of Kodi - https://kodi.tv
*
* SPDX-License-Identifier: GPL-2.0-or-later
* See LICENSES/README.md for more information.
*/
#pragma once
#include "cores/RetroPlayer/shaders/IShaderTexture.h"
#include <memory>
#include <d3d11.h>
class CD3DTexture;
namespace KODI
{
namespace SHADER
{
/*!
* \brief Shader texture that manages the lifetime of its texture object
*/
class CShaderTextureDX : public IShaderTexture
{
public:
explicit CShaderTextureDX(std::shared_ptr<CD3DTexture> texture);
~CShaderTextureDX() override = default;
// Implementation of IShaderTexture
float GetWidth() const override;
float GetHeight() const override;
// DirectX interface
CD3DTexture& GetTexture() { return *m_texture; }
const CD3DTexture& GetTexture() const { return *m_texture; }
ID3D11ShaderResourceView* GetShaderResource() const;
private:
// Construction parameter
const std::shared_ptr<CD3DTexture> m_texture;
};
} // namespace SHADER
} // namespace KODI

View File

@ -0,0 +1,33 @@
/*
* Copyright (C) 2019-2025 Team Kodi
* This file is part of Kodi - https://kodi.tv
*
* SPD3D-License-Identifier: GPL-2.0-or-later
* See LICENSES/README.md for more information.
*/
#include "ShaderTextureDXRef.h"
#include "guilib/D3DResource.h"
using namespace KODI;
using namespace SHADER;
CShaderTextureDXRef::CShaderTextureDXRef(CD3DTexture& texture) : m_texture(texture)
{
}
float CShaderTextureDXRef::GetWidth() const
{
return static_cast<float>(m_texture.GetWidth());
}
float CShaderTextureDXRef::GetHeight() const
{
return static_cast<float>(m_texture.GetHeight());
}
ID3D11ShaderResourceView* CShaderTextureDXRef::GetShaderResource() const
{
return m_texture.GetShaderResource();
}

View File

@ -0,0 +1,50 @@
/*
* Copyright (C) 2017-2025 Team Kodi
* This file is part of Kodi - https://kodi.tv
*
* SPDX-License-Identifier: GPL-2.0-or-later
* See LICENSES/README.md for more information.
*/
#pragma once
#include "cores/RetroPlayer/shaders/IShaderTexture.h"
#include <memory>
#include <d3d11.h>
class CD3DTexture;
namespace KODI
{
namespace SHADER
{
/*!
* \brief Shader texture that wraps an external texture object
*
* NOTE: The lifetime of the external texture object must outlast this class.
*/
class CShaderTextureDXRef : public IShaderTexture
{
public:
explicit CShaderTextureDXRef(CD3DTexture& texture);
~CShaderTextureDXRef() override = default;
// Implementation of IShaderTexture
float GetWidth() const override;
float GetHeight() const override;
// DirectX interface
CD3DTexture& GetTexture() { return m_texture; }
const CD3DTexture& GetTexture() const { return m_texture; }
ID3D11ShaderResourceView* GetShaderResource() const;
private:
// Construction parameter
CD3DTexture& m_texture;
};
} // namespace SHADER
} // namespace KODI

View File

@ -0,0 +1,27 @@
/*
* Copyright (C) 2017-2025 Team Kodi
* This file is part of Kodi - https://kodi.tv
*
* SPDX-License-Identifier: GPL-2.0-or-later
* See LICENSES/README.md for more information.
*/
#pragma once
#include <minwindef.h>
namespace KODI
{
namespace SHADER
{
struct CUSTOMVERTEX
{
FLOAT x;
FLOAT y;
FLOAT z;
FLOAT tu;
FLOAT tv;
};
} // namespace SHADER
} // namespace KODI

View File

@ -0,0 +1,39 @@
/*
* Copyright (C) 2017-2025 Team Kodi
* This file is part of Kodi - https://kodi.tv
*
* SPDX-License-Identifier: GPL-2.0-or-later
* See LICENSES/README.md for more information.
*/
#include "ShaderUtilsDX.h"
using namespace KODI;
using namespace SHADER;
D3D11_TEXTURE_ADDRESS_MODE CShaderUtilsDX::TranslateWrapType(WrapType wrapType)
{
D3D11_TEXTURE_ADDRESS_MODE dxWrap;
switch (wrapType)
{
case WrapType::EDGE:
dxWrap = D3D11_TEXTURE_ADDRESS_CLAMP;
break;
case WrapType::REPEAT:
dxWrap = D3D11_TEXTURE_ADDRESS_WRAP;
break;
case WrapType::MIRRORED_REPEAT:
dxWrap = D3D11_TEXTURE_ADDRESS_MIRROR;
break;
case WrapType::BORDER:
default:
dxWrap = D3D11_TEXTURE_ADDRESS_BORDER;
break;
}
return dxWrap;
}
DirectX::XMFLOAT2 CShaderUtilsDX::ToDXVector(const float2& vec)
{
return DirectX::XMFLOAT2(static_cast<float>(vec.x), static_cast<float>(vec.y));
}

View File

@ -0,0 +1,27 @@
/*
* Copyright (C) 2017-2025 Team Kodi
* This file is part of Kodi - https://kodi.tv
*
* SPDX-License-Identifier: GPL-2.0-or-later
* See LICENSES/README.md for more information.
*/
#pragma once
#include "cores/RetroPlayer/shaders/ShaderTypes.h"
#include <DirectXMath.h>
#include <d3d11.h>
namespace KODI
{
namespace SHADER
{
class CShaderUtilsDX
{
public:
static D3D11_TEXTURE_ADDRESS_MODE TranslateWrapType(WrapType wrapType);
static DirectX::XMFLOAT2 ToDXVector(const float2& vec);
};
} // namespace SHADER
} // namespace KODI

View File

@ -62,6 +62,7 @@ const std::set<AddonType> infoProviderTypes = {
AddonType::SCRAPER_MUSICVIDEOS, AddonType::SCRAPER_TVSHOWS,
};
// clang-format off
const std::set<AddonType> lookAndFeelTypes = {
AddonType::SKIN,
AddonType::SCREENSAVER,
@ -75,9 +76,11 @@ const std::set<AddonType> lookAndFeelTypes = {
const std::set<AddonType> gameTypes = {
AddonType::GAME_CONTROLLER,
AddonType::GAMEDLL,
AddonType::SHADERDLL,
AddonType::GAME,
AddonType::RESOURCE_GAMES,
};
// clang-format on
static bool IsInfoProviderType(AddonType type)
{
@ -127,9 +130,15 @@ static bool IsGameResource(const AddonPtr& addon)
static bool IsGameSupportAddon(const AddonPtr& addon)
{
return addon->Type() == AddonType::GAMEDLL &&
!std::static_pointer_cast<GAME::CGameClient>(addon)->SupportsPath() &&
!std::static_pointer_cast<GAME::CGameClient>(addon)->SupportsStandalone();
if (addon->Type() == AddonType::GAMEDLL &&
!std::static_pointer_cast<GAME::CGameClient>(addon)->SupportsPath() &&
!std::static_pointer_cast<GAME::CGameClient>(addon)->SupportsStandalone())
return true;
if (addon->Type() == AddonType::SHADERDLL)
return true;
return false;
}
static bool IsGameAddon(const AddonPtr& addon)

View File

@ -10,6 +10,7 @@
#include "controllers/Controller.h"
#include "controllers/ControllerManager.h"
#include "cores/RetroPlayer/shaders/ShaderPresetFactory.h"
#include "games/GameSettings.h"
#include "games/GameUtils.h"
#include "games/agents/input/AgentInput.h"
@ -22,12 +23,14 @@ CGameServices::CGameServices(CControllerManager& controllerManager,
RETRO::CGUIGameRenderManager& renderManager,
PERIPHERALS::CPeripherals& peripheralManager,
const CProfileManager& profileManager,
CInputManager& inputManager)
CInputManager& inputManager,
ADDON::CAddonMgr& addons)
: m_controllerManager(controllerManager),
m_gameRenderManager(renderManager),
m_profileManager(profileManager),
m_gameSettings(new CGameSettings()),
m_agentInput(std::make_unique<CAgentInput>(peripheralManager, inputManager))
m_agentInput(std::make_unique<CAgentInput>(peripheralManager, inputManager)),
m_videoShaders(new SHADER::CShaderPresetFactory(addons))
{
// Load the add-ons from the database asynchronously
m_initializationTask =

View File

@ -17,6 +17,11 @@
class CInputManager;
class CProfileManager;
namespace ADDON
{
class CAddonMgr;
} // namespace ADDON
namespace PERIPHERALS
{
class CPeripherals;
@ -29,6 +34,11 @@ namespace RETRO
class CGUIGameRenderManager;
}
namespace SHADER
{
class CShaderPresetFactory;
}
namespace GAME
{
class CAgentInput;
@ -45,7 +55,8 @@ public:
RETRO::CGUIGameRenderManager& renderManager,
PERIPHERALS::CPeripherals& peripheralManager,
const CProfileManager& profileManager,
CInputManager& inputManager);
CInputManager& inputManager,
ADDON::CAddonMgr& addons);
~CGameServices();
ControllerPtr GetController(const std::string& controllerId);
@ -73,6 +84,8 @@ public:
CAgentInput& AgentInput() { return *m_agentInput; }
SHADER::CShaderPresetFactory& VideoShaders() { return *m_videoShaders; }
/*!
* \brief Called when an add-on repo is installed
*
@ -90,6 +103,7 @@ private:
// Game services
std::unique_ptr<CGameSettings> m_gameSettings;
std::unique_ptr<CAgentInput> m_agentInput;
std::unique_ptr<SHADER::CShaderPresetFactory> m_videoShaders;
// Game threads
std::future<void> m_initializationTask;

View File

@ -14,6 +14,9 @@ constexpr auto SAVESTATE_CAPTION = "savestate.caption";
constexpr auto SAVESTATE_GAME_CLIENT = "savestate.gameclient";
constexpr auto SAVESTATE_GAME_CLIENT_VERSION = "savestate.gameclientversion";
// String of list item property "game.videofilter" when no filter is set
constexpr auto PROPERTY_NO_VIDEO_FILTER = "";
// Control IDs for game dialogs
constexpr unsigned int CONTROL_VIDEO_HEADING = 10810;
constexpr unsigned int CONTROL_VIDEO_THUMBS = 10811;

View File

@ -8,18 +8,32 @@
#include "DialogGameVideoFilter.h"
#include "ServiceBroker.h"
#include "URL.h"
#include "cores/RetroPlayer/guibridge/GUIGameVideoHandle.h"
#include "cores/RetroPlayer/rendering/RenderVideoSettings.h"
#include "cores/RetroPlayer/shaders/ShaderPresetFactory.h"
#include "filesystem/File.h"
#include "filesystem/SpecialProtocol.h"
#include "games/GameServices.h"
#include "games/dialogs/DialogGameDefines.h"
#include "guilib/LocalizeStrings.h"
#include "guilib/WindowIDs.h"
#include "settings/GameSettings.h"
#include "settings/MediaSettings.h"
#include "utils/StringUtils.h"
#include "utils/URIUtils.h"
#include "utils/Variant.h"
#include "utils/XBMCTinyXML.h"
#include "utils/log.h"
#include <stdlib.h>
using namespace KODI;
using namespace GAME;
#define PRESETS_ADDON_NAME "game.shader.presets"
namespace
{
struct ScalingMethodProperties
@ -50,6 +64,7 @@ void CDialogGameVideoFilter::PreInit()
{
m_items.Clear();
InitScalingMethods();
InitVideoFilters();
if (m_items.Size() == 0)
@ -61,7 +76,7 @@ void CDialogGameVideoFilter::PreInit()
m_bHasDescription = false;
}
void CDialogGameVideoFilter::InitVideoFilters()
void CDialogGameVideoFilter::InitScalingMethods()
{
if (m_gameVideoHandle)
{
@ -84,6 +99,97 @@ void CDialogGameVideoFilter::InitVideoFilters()
}
}
void CDialogGameVideoFilter::InitVideoFilters()
{
std::vector<VideoFilterProperties> videoFilters;
std::string xmlPath;
std::unique_ptr<CXBMCTinyXML> xml;
// TODO: Have the add-on give us the xml as a string (or parse it)
std::string xmlFilename;
#ifdef TARGET_WINDOWS
xmlFilename = "ShaderPresetsHLSLP.xml";
#else
xmlFilename = "ShaderPresetsGLSLP.xml";
#endif
const std::string homeAddonPath = CSpecialProtocol::TranslatePath(
URIUtils::AddFileToFolder("special://home", "addons", PRESETS_ADDON_NAME));
const std::string systemAddonPath = CSpecialProtocol::TranslatePath(
URIUtils::AddFileToFolder("special://xbmc", "addons", PRESETS_ADDON_NAME));
const std::string binAddonPath = CSpecialProtocol::TranslatePath(
URIUtils::AddFileToFolder("special://xbmcbinaddons", PRESETS_ADDON_NAME));
for (const auto& basePath : {homeAddonPath, systemAddonPath, binAddonPath})
{
xmlPath = URIUtils::AddFileToFolder(basePath, "resources", xmlFilename);
CLog::LogF(LOGERROR, "Looking for shader preset XML at {}", CURL::GetRedacted(xmlPath));
if (XFILE::CFile::Exists(xmlPath))
{
xml = std::make_unique<CXBMCTinyXML>(xmlPath);
if (xml->LoadFile())
break;
CLog::LogF(LOGERROR, "Couldn't load shader presets from XML, {}", CURL::GetRedacted(xmlPath));
xml.reset();
}
}
if (!xml)
return;
auto root = xml->RootElement();
TiXmlNode* child = nullptr;
while ((child = root->IterateChildren(child)))
{
VideoFilterProperties videoFilter;
if (child->FirstChild() == nullptr)
continue;
TiXmlNode* pathNode;
if ((pathNode = child->FirstChild("path")))
if ((pathNode = pathNode->FirstChild()))
videoFilter.path =
URIUtils::AddFileToFolder(URIUtils::GetBasePath(xmlPath), pathNode->Value());
TiXmlNode* nameNode;
if ((nameNode = child->FirstChild("name")))
if ((nameNode = nameNode->FirstChild()))
videoFilter.name = nameNode->Value();
TiXmlNode* folderNode;
if ((folderNode = child->FirstChild("folder")))
if ((folderNode = folderNode->FirstChild()))
videoFilter.folder = folderNode->Value();
videoFilters.emplace_back(videoFilter);
}
CLog::Log(LOGDEBUG, "Loaded {} shader presets from default XML, {}", videoFilters.size(),
CURL::GetRedacted(xmlPath));
for (const auto& videoFilter : videoFilters)
{
bool canLoadPreset =
CServiceBroker::GetGameServices().VideoShaders().CanLoadPreset(videoFilter.path);
if (!canLoadPreset)
continue;
CFileItem item{videoFilter.name};
item.SetLabel2(videoFilter.folder);
item.SetProperty("game.videofilter", CVariant{videoFilter.path});
m_items.Add(std::move(item));
}
}
void CDialogGameVideoFilter::GetItems(CFileItemList& items)
{
for (const auto& item : m_items)
@ -100,7 +206,7 @@ void CDialogGameVideoFilter::OnItemFocus(unsigned int index)
std::string description;
GetProperties(*item, videoFilter, description);
CGameSettings& gameSettings = CMediaSettings::GetInstance().GetCurrentGameSettings();
::CGameSettings& gameSettings = CMediaSettings::GetInstance().GetCurrentGameSettings();
if (gameSettings.VideoFilter() != videoFilter)
{
@ -120,7 +226,7 @@ void CDialogGameVideoFilter::OnItemFocus(unsigned int index)
unsigned int CDialogGameVideoFilter::GetFocusedItem() const
{
CGameSettings& gameSettings = CMediaSettings::GetInstance().GetCurrentGameSettings();
::CGameSettings& gameSettings = CMediaSettings::GetInstance().GetCurrentGameSettings();
for (int i = 0; i < m_items.Size(); i++)
{
@ -148,6 +254,11 @@ bool CDialogGameVideoFilter::OnClickAction()
return true;
}
std::string CDialogGameVideoFilter::GetLocalizedString(uint32_t code)
{
return g_localizeStrings.GetAddonString(PRESETS_ADDON_NAME, code);
}
void CDialogGameVideoFilter::GetProperties(const CFileItem& item,
std::string& videoFilter,
std::string& description)

View File

@ -36,6 +36,7 @@ protected:
bool OnClickAction() override;
private:
void InitScalingMethods();
void InitVideoFilters();
static void GetProperties(const CFileItem& item,
@ -44,6 +45,15 @@ private:
CFileItemList m_items;
static std::string GetLocalizedString(uint32_t code);
struct VideoFilterProperties
{
std::string path;
std::string name;
std::string folder;
};
//! \brief Set to true when a description has first been set
bool m_bHasDescription = false;
};