diff --git a/cmake/treedata/linux/subdirs.txt b/cmake/treedata/linux/subdirs.txt index 35c8d88c15731..4ff5896cee43e 100644 --- a/cmake/treedata/linux/subdirs.txt +++ b/cmake/treedata/linux/subdirs.txt @@ -11,4 +11,5 @@ xbmc/platform/posix platform/posix xbmc/platform/posix/filesystem platform/posix/filesystem xbmc/platform/posix/network platform/posix/network xbmc/platform/posix/utils platform/posix/utils +xbmc/cores/RetroPlayer/shaders/gl cores/RetroPlayer/shaders/gl xbmc/windowing/linux windowing/linux diff --git a/cmake/treedata/osx/subdirs.txt b/cmake/treedata/osx/subdirs.txt index fa28aa0efba62..c2fb6b897f60a 100644 --- a/cmake/treedata/osx/subdirs.txt +++ b/cmake/treedata/osx/subdirs.txt @@ -13,3 +13,4 @@ xbmc/platform/posix/filesystem platform/posix/filesystem xbmc/platform/posix/network platform/posix/network xbmc/platform/posix/utils platform/posix/utils xbmc/windowing/osx windowing/osx +xbmc/cores/RetroPlayer/shaders/gl cores/RetroPlayer/shaders/gl diff --git a/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGL.cpp b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGL.cpp index f4400d67dd103..3472df2d9450c 100644 --- a/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGL.cpp +++ b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGL.cpp @@ -11,6 +11,7 @@ #include "cores/RetroPlayer/buffers/RenderBufferOpenGL.h" #include "cores/RetroPlayer/buffers/RenderBufferPoolOpenGL.h" #include "cores/RetroPlayer/rendering/RenderContext.h" +#include "cores/RetroPlayer/shaders/gl/ShaderPresetGL.h" #include "utils/log.h" #include @@ -45,6 +46,9 @@ CRPRendererOpenGL::CRPRendererOpenGL(const CRenderSettings& renderSettings, std::shared_ptr bufferPool) : CRPBaseRenderer(renderSettings, context, std::move(bufferPool)) { + // Initialize CRPBaseRenderer + m_shaderPreset.reset(new SHADER::CShaderPresetGL(m_context)); + // Initialize CRPRendererOpenGL m_clearColour = m_context.UseLimitedColor() ? (16.0f / 0xff) : 0.0f; @@ -277,60 +281,103 @@ void CRPRendererOpenGL::Render(uint8_t alpha) const uint32_t color = (alpha << 24) | 0xFFFFFF; - glBindTexture(m_textureTarget, renderBuffer->TextureID()); + RenderBufferTextures* rbTextures; + const auto it = m_RBTexturesMap.find(renderBuffer); + if (it != m_RBTexturesMap.end()) + { + rbTextures = it->second.get(); + } + else + { + // We can't copy or move CGLTexture, so construct source/target in-place + rbTextures = new RenderBufferTextures{{// source texture + static_cast(renderBuffer->GetWidth()), + static_cast(renderBuffer->GetHeight()), + GL_RGB, renderBuffer->TextureID()}, + {// target texture + static_cast(m_context.GetScreenWidth()), + static_cast(m_context.GetScreenHeight())}}; + m_RBTexturesMap.emplace(renderBuffer, rbTextures); + } + + const auto sourceTexture = &rbTextures->source; + const auto targetTexture = &rbTextures->target; + + glBindTexture(m_textureTarget, sourceTexture->getMTexture()); GLint filter = GL_NEAREST; if (GetRenderSettings().VideoSettings().GetScalingMethod() == SCALINGMETHOD::LINEAR) filter = GL_LINEAR; + glTexParameteri(m_textureTarget, GL_TEXTURE_MAG_FILTER, filter); glTexParameteri(m_textureTarget, GL_TEXTURE_MIN_FILTER, filter); glTexParameteri(m_textureTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(m_textureTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - m_context.EnableGUIShader(GL_SHADER_METHOD::TEXTURE); + Updateshaders(); + + if (m_bUseShaderPreset) + { + const CPoint destPoints[4] = {m_rotatedDestCoords[0], m_rotatedDestCoords[1], + m_rotatedDestCoords[2], m_rotatedDestCoords[3]}; + + targetTexture->CreateTextureObject(); + + SHADER::CShaderTextureGL source(*sourceTexture); + SHADER::CShaderTextureGL target(*targetTexture); + if (!m_shaderPreset->RenderUpdate(destPoints, &source, &target)) + { + m_shadersNeedUpdate = false; + m_bUseShaderPreset = false; + } + } + else + { + m_context.EnableGUIShader(GL_SHADER_METHOD::TEXTURE); - GLubyte colour[4]; - GLubyte idx[4] = {0, 1, 3, 2}; // Determines order of triangle strip - PackedVertex vertex[4]; + GLubyte colour[4]; + GLubyte idx[4] = {0, 1, 3, 2}; // Determines order of triangle strip + PackedVertex vertex[4]; - GLint uniColLoc = m_context.GUIShaderGetUniCol(); + GLint uniColLoc = m_context.GUIShaderGetUniCol(); - // Setup color values - colour[0] = static_cast(GET_R(color)); - colour[1] = static_cast(GET_G(color)); - colour[2] = static_cast(GET_B(color)); - colour[3] = static_cast(GET_A(color)); + // Setup color values + colour[0] = static_cast(GET_R(color)); + colour[1] = static_cast(GET_G(color)); + colour[2] = static_cast(GET_B(color)); + colour[3] = static_cast(GET_A(color)); - for (unsigned int i = 0; i < 4; i++) - { - // Setup vertex position values - vertex[i].x = m_rotatedDestCoords[i].x; - vertex[i].y = m_rotatedDestCoords[i].y; - vertex[i].z = 0.0f; - } + for (unsigned int i = 0; i < 4; i++) + { + // Setup vertex position values + vertex[i].x = m_rotatedDestCoords[i].x; + vertex[i].y = m_rotatedDestCoords[i].y; + vertex[i].z = 0.0f; + } - // Setup texture coordinates - vertex[0].u1 = vertex[3].u1 = rect.x1; - vertex[0].v1 = vertex[1].v1 = rect.y1; - vertex[1].u1 = vertex[2].u1 = rect.x2; - vertex[2].v1 = vertex[3].v1 = rect.y2; + // Setup texture coordinates + vertex[0].u1 = vertex[3].u1 = rect.x1; + vertex[0].v1 = vertex[1].v1 = rect.y1; + vertex[1].u1 = vertex[2].u1 = rect.x2; + vertex[2].v1 = vertex[3].v1 = rect.y2; - glBindVertexArray(m_mainVAO); + glBindVertexArray(m_mainVAO); - glBindBuffer(GL_ARRAY_BUFFER, m_mainVertexVBO); - glBufferData(GL_ARRAY_BUFFER, sizeof(PackedVertex) * 4, &vertex[0], GL_STATIC_DRAW); + glBindBuffer(GL_ARRAY_BUFFER, m_mainVertexVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(PackedVertex) * 4, &vertex[0], GL_STATIC_DRAW); - // No need to bind the index VBO, it's part of VAO state - glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLubyte) * 4, idx, GL_STATIC_DRAW); + // No need to bind the index VBO, it's part of VAO state + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLubyte) * 4, idx, GL_STATIC_DRAW); - glUniform4f(uniColLoc, (colour[0] / 255.0f), (colour[1] / 255.0f), (colour[2] / 255.0f), - (colour[3] / 255.0f)); + glUniform4f(uniColLoc, (colour[0] / 255.0f), (colour[1] / 255.0f), (colour[2] / 255.0f), + (colour[3] / 255.0f)); - glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, 0); + glDrawElements(GL_TRIANGLE_STRIP, 4, GL_UNSIGNED_BYTE, 0); - // Unbind VAO/VBO just to be safe - glBindVertexArray(0); - glBindBuffer(GL_ARRAY_BUFFER, 0); + // Unbind VAO/VBO just to be safe + glBindVertexArray(0); + glBindBuffer(GL_ARRAY_BUFFER, 0); - m_context.DisableGUIShader(); + m_context.DisableGUIShader(); + } } diff --git a/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGL.h b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGL.h index be9e4f294feed..b21cfb3e7f6fb 100644 --- a/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGL.h +++ b/xbmc/cores/RetroPlayer/rendering/VideoRenderers/RPRendererOpenGL.h @@ -10,6 +10,10 @@ #include "RPBaseRenderer.h" #include "cores/RetroPlayer/process/RPProcessInfo.h" +#include "guilib/TextureGL.h" + +#include +#include #include "system_gl.h" @@ -18,6 +22,7 @@ namespace KODI namespace RETRO { class CRenderContext; +class CRenderBufferOpenGL; class CRendererFactoryOpenGL : public IRendererFactory { @@ -58,6 +63,11 @@ class CRPRendererOpenGL : public CRPBaseRenderer float y; float z; }; + struct RenderBufferTextures + { + CGLTexture source; + CGLTexture target; + }; // implementation of CRPBaseRenderer void RenderInternal(bool clear, uint8_t alpha) override; @@ -78,6 +88,8 @@ class CRPRendererOpenGL : public CRPBaseRenderer virtual void Render(uint8_t alpha); + std::map> m_RBTexturesMap; + GLuint m_mainVAO; GLuint m_mainVertexVBO; GLuint m_mainIndexVBO; diff --git a/xbmc/cores/RetroPlayer/shaders/IShader.h b/xbmc/cores/RetroPlayer/shaders/IShader.h index 800e982363c21..3c43cb05e5833 100644 --- a/xbmc/cores/RetroPlayer/shaders/IShader.h +++ b/xbmc/cores/RetroPlayer/shaders/IShader.h @@ -66,15 +66,6 @@ class IShader */ virtual bool CreateVertexBuffer(unsigned vertCount, unsigned vertSize) = 0; - /*! - * \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. - */ - // TODO: the first argument is DX-specific (maybe the entire function is) - virtual bool CreateInputLayout(D3D11_INPUT_ELEMENT_DESC* layout, unsigned numElements) = 0; - /*! * \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. diff --git a/xbmc/cores/RetroPlayer/shaders/gl/CMakeLists.txt b/xbmc/cores/RetroPlayer/shaders/gl/CMakeLists.txt new file mode 100644 index 0000000000000..b23c78aabe9fa --- /dev/null +++ b/xbmc/cores/RetroPlayer/shaders/gl/CMakeLists.txt @@ -0,0 +1,16 @@ +set(SOURCES ShaderGL.cpp + ShaderLutGL.cpp + ShaderPresetGL.cpp + ShaderTextureGL.cpp + ShaderUtilsGL.cpp +) + +set(HEADERS ShaderGL.h + ShaderLutGL.h + ShaderPresetGL.h + ShaderTextureGL.h + ShaderTypesGL.h + ShaderUtilsGL.h +) + +core_add_library(rp-shaders-gl) diff --git a/xbmc/cores/RetroPlayer/shaders/gl/ShaderGL.cpp b/xbmc/cores/RetroPlayer/shaders/gl/ShaderGL.cpp new file mode 100644 index 0000000000000..cae90d9405fb6 --- /dev/null +++ b/xbmc/cores/RetroPlayer/shaders/gl/ShaderGL.cpp @@ -0,0 +1,297 @@ +/* + * Copyright (C) 2019 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 "ShaderGL.h" + +#include "Application.h" +#include "ShaderTextureGL.h" +#include "ShaderUtilsGL.h" +#include "cores/RetroPlayer/rendering/RenderContext.h" +#include "cores/RetroPlayer/shaders/IShaderLut.h" +#include "rendering/gl/RenderSystemGL.h" +#include "utils/URIUtils.h" +#include "utils/log.h" + +using namespace KODI; +using namespace SHADER; + +CShaderGL::CShaderGL(RETRO::CRenderContext& context) : m_context(context) +{ +} + +bool CShaderGL::Create(const std::string& shaderSource, + const std::string& shaderPath, + ShaderParameterMap shaderParameters, + IShaderSampler* sampler, + ShaderLutVec luts, + float2 viewPortSize, + unsigned frameCountMod) +{ + //! @todo Remove sampler input from IShader.h + if (shaderPath.empty()) + { + CLog::Log(LOGERROR, "ShaderGL: Can't load empty shader path"); + return false; + } + + m_shaderSource = shaderSource; + m_shaderPath = shaderPath; + m_shaderParameters = shaderParameters; + m_luts = luts; + m_viewportSize = viewPortSize; + m_frameCountMod = frameCountMod; + + std::string defineVertex = "#define VERTEX\n"; + std::string defineFragment; + + if (m_shaderParameters.empty()) + defineFragment = "#define FRAGMENT\n"; + else + defineFragment = "#define FRAGMENT\n#define PARAMETER_UNIFORM\n"; + + if (m_shaderSource.rfind("#version", 0) == 0) + { + CShaderUtilsGL::MoveVersionToFirstLine(m_shaderSource, defineVertex, defineFragment); + } + + std::string vertexShaderSourceStr = defineVertex + m_shaderSource; + std::string fragmentShaderSourceStr = defineFragment + m_shaderSource; + const char* vertexShaderSource = vertexShaderSourceStr.c_str(); + const char* fragmentShaderSource = fragmentShaderSourceStr.c_str(); + + GLuint vShader; + vShader = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(vShader, 1, &vertexShaderSource, NULL); + glCompileShader(vShader); + + GLuint fShader; + fShader = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(fShader, 1, &fragmentShaderSource, NULL); + glCompileShader(fShader); //! @todo Make this good + + m_shaderProgram = glCreateProgram(); + glAttachShader(m_shaderProgram, vShader); + glAttachShader(m_shaderProgram, fShader); + glBindAttribLocation(m_shaderProgram, 0, "VertexCoord"); + glBindAttribLocation(m_shaderProgram, 1, "TexCoord"); + glBindAttribLocation(m_shaderProgram, 2, "COLOR"); + + glLinkProgram(m_shaderProgram); + glDeleteShader(vShader); + glDeleteShader(fShader); + + glUseProgram(m_shaderProgram); + + glGenVertexArrays(1, &VAO); + glGenBuffers(3, VBO); + glGenBuffers(1, &EBO); + return true; +} + +void CShaderGL::Render(IShaderTexture* source, IShaderTexture* target) +{ + CShaderTextureGL* sourceGL = static_cast(source); + GLuint texture = sourceGL->GetPointer()->getMTexture(); + glUseProgram(m_shaderProgram); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture); + + // for (int i = 0; i < m_luts.size(); ++i) + // { + // auto* lutTexture = dynamic_cast(m_luts[i].get()->GetTexture()); + // if (lutTexture) + // { + // glActiveTexture(GL_TEXTURE1 + i); + // GLuint lutTextureID = lutTexture->GetPointer()->getMTexture(); + // glBindTexture(GL_TEXTURE_2D, lutTextureID); + // } + // } + + glBindVertexArray(VAO); + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); +} + +void CShaderGL::SetShaderParameters() +{ + glUseProgram(m_shaderProgram); + glUniformMatrix4fv(m_MVPMatrixLoc, 1, GL_FALSE, reinterpret_cast(&m_MVP)); + + glBindVertexArray(VAO); + + glBindBuffer(GL_ARRAY_BUFFER, VBO[0]); + glBufferData(GL_ARRAY_BUFFER, sizeof(m_VertexCoords), m_VertexCoords, GL_STATIC_DRAW); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); + glEnableVertexAttribArray(0); + + glBindBuffer(GL_ARRAY_BUFFER, VBO[1]); + glBufferData(GL_ARRAY_BUFFER, sizeof(m_colors), m_colors, GL_STATIC_DRAW); + glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); + glEnableVertexAttribArray(2); + + glBindBuffer(GL_ARRAY_BUFFER, VBO[2]); + glBufferData(GL_ARRAY_BUFFER, sizeof(m_TexCoords), m_TexCoords, GL_STATIC_DRAW); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (void*)0); + glEnableVertexAttribArray(1); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(m_indices), m_indices, GL_STATIC_DRAW); + + for (const auto& parameter : m_shaderParameters) + { + GLint paramLoc = glGetUniformLocation(m_shaderProgram, parameter.first.c_str()); + glUniform1f(paramLoc, parameter.second); + } +} + +void CShaderGL::PrepareParameters(CPoint* dest, bool isLastPass, uint64_t frameCount) +{ + UpdateInputBuffer(frameCount); + + if (!isLastPass) + { + // bottom left x,y + m_VertexCoords[0][0] = -m_outputSize.x / 2; + m_VertexCoords[0][1] = -m_outputSize.y / 2; + // bottom right x,y + m_VertexCoords[1][0] = m_outputSize.x / 2; + m_VertexCoords[1][1] = -m_outputSize.y / 2; + // top right x,y + m_VertexCoords[2][0] = m_outputSize.x / 2; + m_VertexCoords[2][1] = m_outputSize.y / 2; + // top left x,y + m_VertexCoords[3][0] = -m_outputSize.x / 2; + m_VertexCoords[3][1] = m_outputSize.y / 2; + } + else // last pass + { + // bottom left x,y + m_VertexCoords[0][0] = dest[3].x - m_outputSize.x / 2; + m_VertexCoords[0][1] = dest[3].y - m_outputSize.y / 2; + // bottom right x,y + m_VertexCoords[1][0] = dest[2].x - m_outputSize.x / 2; + m_VertexCoords[1][1] = dest[2].y - m_outputSize.y / 2; + // top right x,y + m_VertexCoords[2][0] = dest[1].x - m_outputSize.x / 2; + m_VertexCoords[2][1] = dest[1].y - m_outputSize.y / 2; + // top left x,y + m_VertexCoords[3][0] = dest[0].x - m_outputSize.x / 2; + m_VertexCoords[3][1] = dest[0].y - m_outputSize.y / 2; + } + + // bottom left z, tu, tv, r, g, b + m_VertexCoords[0][2] = 0; + m_TexCoords[0][0] = 0.0f; + m_TexCoords[0][1] = 1.0f; + m_colors[0][0] = 0.0f; + m_colors[0][1] = 0.0f; + m_colors[0][2] = 0.0f; + + // bottom right z, tu, tv, r, g, b + m_VertexCoords[1][2] = 0; + m_TexCoords[1][0] = 1.0f; + m_TexCoords[1][1] = 1.0f; + m_colors[1][0] = 0.0f; + m_colors[1][1] = 0.0f; + m_colors[1][2] = 0.0f; + + // top right z, tu, tv, r, g, b + m_VertexCoords[2][2] = 0; + m_TexCoords[2][0] = 1.0f; + m_TexCoords[2][1] = 0.0f; + m_colors[2][0] = 0.0f; + m_colors[2][1] = 0.0f; + m_colors[2][2] = 0.0f; + + // top left z, tu, tv, r, g, b + m_VertexCoords[3][2] = 0; + m_TexCoords[3][0] = 0.0f; + m_TexCoords[3][1] = 0.0f; + m_colors[3][0] = 0.0f; + m_colors[3][1] = 0.0f; + m_colors[3][2] = 0.0f; + + m_indices[0][0] = 0; + m_indices[0][1] = 1; + m_indices[0][2] = 3; + m_indices[1][0] = 1; + m_indices[1][1] = 2; + m_indices[1][2] = 3; + + SetShaderParameters(); +} + +void CShaderGL::UpdateMVP() +{ + GLfloat xScale = 1.0f / m_outputSize.x * 2.0f; + GLfloat yScale = -1.0f / m_outputSize.y * 2.0f; + + // Update projection matrix + m_MVP = {{{xScale, 0, 0, 0}, {0, yScale, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, 1}}}; +} + +void CShaderGL::GetUniformLocs() +{ + m_FrameDirectionLoc = glGetUniformLocation(m_shaderProgram, "FrameDirection"); + m_FrameCountLoc = glGetUniformLocation(m_shaderProgram, "FrameCount"); + m_OutputSizeLoc = glGetUniformLocation(m_shaderProgram, "OutputSize"); + m_TextureSizeLoc = glGetUniformLocation(m_shaderProgram, "TextureSize"); + m_InputSizeLoc = glGetUniformLocation(m_shaderProgram, "InputSize"); + m_MVPMatrixLoc = glGetUniformLocation(m_shaderProgram, "MVPMatrix"); +} + +//! @todo Change name of this method in IShader.h to CreateInputs +bool CShaderGL::CreateInputBuffer() +{ + GetUniformLocs(); + UpdateInputBuffer(0); + return true; +} + +//! @todo Change name of this method in IShader.h to UpdateInputs +void CShaderGL::UpdateInputBuffer(uint64_t frameCount) +{ + glUseProgram(m_shaderProgram); + uniformInputs inputInitData = GetInputData(frameCount); + glUniform1f(m_FrameDirectionLoc, inputInitData.frame_direction); + glUniform1i(m_FrameCountLoc, inputInitData.frame_count); + glUniform2f(m_OutputSizeLoc, inputInitData.output_size.x, inputInitData.output_size.y); + glUniform2f(m_TextureSizeLoc, inputInitData.texture_size.x, inputInitData.texture_size.y); + glUniform2f(m_InputSizeLoc, inputInitData.video_size.x, inputInitData.video_size.y); +} + +CShaderGL::uniformInputs CShaderGL::GetInputData(uint64_t frameCount) +{ + if (m_frameCountMod != 0) + frameCount %= m_frameCountMod; + + uniformInputs input = { + // Resolution of texture passed to the shader + {m_inputSize}, // video_size + {m_inputSize}, // texture_size + // As per the spec, this is the viewport resolution (not the + // output res of each shader) + {m_viewportSize}, // output_size + // Current frame count that can be modulo'ed + static_cast(frameCount), // frame_count + // Time always flows forward + 1.0f // frame_direction + }; + + return input; +} + +void CShaderGL::SetSizes(const float2& prevSize, const float2& nextSize) +{ + m_inputSize = prevSize; + m_outputSize = nextSize; +} + +bool CShaderGL::CreateVertexBuffer(unsigned vertCount, unsigned vertSize) +{ + return false; +} diff --git a/xbmc/cores/RetroPlayer/shaders/gl/ShaderGL.h b/xbmc/cores/RetroPlayer/shaders/gl/ShaderGL.h new file mode 100644 index 0000000000000..cdf493a93f41e --- /dev/null +++ b/xbmc/cores/RetroPlayer/shaders/gl/ShaderGL.h @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2019 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 "ShaderTextureGL.h" +#include "cores/RetroPlayer/shaders/IShader.h" +#include "cores/RetroPlayer/shaders/gl/ShaderTypesGL.h" +#include "guilib/TextureGL.h" +#include "rendering/gl/GLShader.h" + +#include +#include + +namespace KODI +{ +namespace RETRO +{ + +class CRenderContext; + +} + +namespace SHADER +{ +class CShaderGL : public IShader +{ +public: + CShaderGL(RETRO::CRenderContext& context); + ~CShaderGL() override = default; + + bool CreateVertexBuffer(unsigned vertCount, unsigned vertSize) override; + + // implementation of IShader + bool Create(const std::string& shaderSource, + const std::string& shaderPath, + ShaderParameterMap shaderParameters, + IShaderSampler* sampler, + ShaderLutVec luts, + float2 viewPortSize, + unsigned frameCountMod = 0) override; + void Render(IShaderTexture* source, IShaderTexture* target) override; + void SetSizes(const float2& prevSize, const float2& nextSize) override; + void PrepareParameters(CPoint dest[4], bool isLastPass, uint64_t frameCount) override; + void UpdateMVP() override; + bool CreateInputBuffer() override; + void UpdateInputBuffer(uint64_t frameCount); + void GetUniformLocs(); + +protected: + void SetShaderParameters(); + +private: + struct uniformInputs + { + float2 video_size; + float2 texture_size; + float2 output_size; + GLint frame_count; + GLfloat frame_direction; + }; + + // Currently loaded shader's source code + std::string m_shaderSource; + + // Currently loaded shader's relative path + std::string m_shaderPath; + + // Array of shader parameters + ShaderParameterMap m_shaderParameters; + + // Look-up textures that the shader uses + ShaderLutVec m_luts; // todo: back to DX maybe + + // Resolution of the input of the shader + float2 m_inputSize; + + // Resolution of the output of the shader + float2 m_outputSize; + + // Resolution of the viewport/window + float2 m_viewportSize; + + // Resolution of the texture that holds the input + //float2 m_textureSize; + + GLuint m_shaderProgram = 0; + + // Projection matrix + std::array, 4> m_MVP; + + float m_VertexCoords[4][3]; + float m_colors[4][3]; + float m_TexCoords[4][2]; + unsigned int m_indices[2][3]; + + // Value to modulo (%) frame count with + // Unused if 0 + unsigned m_frameCountMod = 0; + + GLint m_FrameDirectionLoc = -1; + GLint m_FrameCountLoc = -1; + GLint m_OutputSizeLoc = -1; + GLint m_TextureSizeLoc = -1; + GLint m_InputSizeLoc = -1; + GLint m_MVPMatrixLoc = -1; + + GLuint VAO = 0; + GLuint EBO = 0; + GLuint VBO[3] = {}; + +private: + uniformInputs GetInputData(uint64_t frameCount = 0); + + // Construction parameters + RETRO::CRenderContext& m_context; +}; +} // namespace SHADER +} // namespace KODI diff --git a/xbmc/cores/RetroPlayer/shaders/gl/ShaderLutGL.cpp b/xbmc/cores/RetroPlayer/shaders/gl/ShaderLutGL.cpp new file mode 100644 index 0000000000000..f0be1bddbdf56 --- /dev/null +++ b/xbmc/cores/RetroPlayer/shaders/gl/ShaderLutGL.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2019 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 "ShaderLutGL.h" + +#include "ShaderTextureGL.h" +#include "ShaderUtilsGL.h" +#include "cores/RetroPlayer/rendering/RenderContext.h" +#include "cores/RetroPlayer/shaders/IShaderPreset.h" +#include "rendering/gl/RenderSystemGL.h" +#include "utils/log.h" + +#include +#define MAX_FLOAT 3.402823466E+38 + +using namespace KODI; +using namespace SHADER; + +CShaderLutGL::CShaderLutGL(const std::string& id, const std::string& path) : IShaderLut(id, path) +{ +} + +CShaderLutGL::~CShaderLutGL() = default; + +bool CShaderLutGL::Create(RETRO::CRenderContext& context, const ShaderLut& lut) +{ + std::unique_ptr lutTexture(CreateLUTTexture(context, lut)); + if (!lutTexture) + { + CLog::Log(LOGWARNING, "%s - Couldn't create a LUT texture for LUT %s", __FUNCTION__, lut.strId); + return false; + } + + m_texture = std::move(lutTexture); + + return true; +} + +std::unique_ptr CShaderLutGL::CreateLUTTexture(RETRO::CRenderContext& context, + const KODI::SHADER::ShaderLut& lut) +{ + auto wrapType = CShaderUtilsGL::TranslateWrapType(lut.wrap); + auto filterType = lut.filter ? GL_LINEAR : GL_NEAREST; + + CGLTexture* texture = static_cast(CGLTexture::LoadFromFile(lut.path)); + + if (!texture) + { + CLog::Log(LOGERROR, "Couldn't open LUT %s", lut.path); + return std::unique_ptr(); + } + + texture->CreateTextureObject(); + glBindTexture(GL_TEXTURE_2D, texture->getMTexture()); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filterType); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filterType); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapType); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapType); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, wrapType); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_NEVER); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_LOD, 0.0); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, MAX_FLOAT); + + GLfloat blackBorder[4] = {0.0f, 0.0f, 0.0f, 0.0f}; + glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, blackBorder); + + if (lut.mipmap) + texture->SetMipmapping(); + + return std::unique_ptr(new CShaderTextureGL(texture)); +} diff --git a/xbmc/cores/RetroPlayer/shaders/gl/ShaderLutGL.h b/xbmc/cores/RetroPlayer/shaders/gl/ShaderLutGL.h new file mode 100644 index 0000000000000..7ba6dadb8fc9b --- /dev/null +++ b/xbmc/cores/RetroPlayer/shaders/gl/ShaderLutGL.h @@ -0,0 +1,58 @@ + +/* + * Copyright (C) 2019 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 +#include + +#include "system_gl.h" + +namespace KODI +{ + +namespace RETRO +{ +class CRenderContext; +} + +namespace SHADER +{ + +class IShaderTexture; +struct ShaderLut; + +class CShaderLutGL : public IShaderLut +{ +public: + CShaderLutGL() = default; + CShaderLutGL(const std::string& id, const std::string& path); + + //Destructor + ~CShaderLutGL() override; + + //Implementation of IShaderLut + bool Create(RETRO::CRenderContext& context, const ShaderLut& lut) override; + IShaderSampler* GetSampler() override { return nullptr; } + IShaderTexture* GetTexture() override { return m_texture.get(); } + +private: + static std::unique_ptr CreateLUTSampler(RETRO::CRenderContext& context, + const ShaderLut& lut); + static std::unique_ptr CreateLUTTexture(RETRO::CRenderContext& context, + const ShaderLut& lut); + + std::unique_ptr m_texture; +}; + +} // namespace SHADER +} // namespace KODI diff --git a/xbmc/cores/RetroPlayer/shaders/gl/ShaderPresetGL.cpp b/xbmc/cores/RetroPlayer/shaders/gl/ShaderPresetGL.cpp new file mode 100644 index 0000000000000..fa9bf6ea64bec --- /dev/null +++ b/xbmc/cores/RetroPlayer/shaders/gl/ShaderPresetGL.cpp @@ -0,0 +1,459 @@ +/* + * Copyright (C) 2019 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 "ShaderPresetGL.h" + +#include "ServiceBroker.h" +#include "cores/RetroPlayer/rendering/RenderContext.h" +#include "cores/RetroPlayer/shaders/ShaderPresetFactory.h" +#include "cores/RetroPlayer/shaders/ShaderUtils.h" +#include "cores/RetroPlayer/shaders/gl/ShaderGL.h" +#include "cores/RetroPlayer/shaders/gl/ShaderLutGL.h" +#include "rendering/gl/RenderSystemGL.h" +#include "utils/log.h" + +#include + +#define MAX_FLOAT 3.402823466E+38 + +using namespace KODI; +using namespace SHADER; + +CShaderPresetGL::CShaderPresetGL(RETRO::CRenderContext& context, + unsigned int videoWidth, + unsigned int videoHeight) + : m_context(context), m_videoSize(videoWidth, videoHeight) +{ + m_textureSize = CShaderUtils::GetOptimalTextureSize(m_videoSize); + + CRect viewPort; + m_context.GetViewPort(viewPort); + m_outputSize = {viewPort.Width(), viewPort.Height()}; +} + +CShaderPresetGL::~CShaderPresetGL() +{ + DisposeShaders(); + + // The gui is going to render after this, so apply the state required + m_context.ApplyStateBlock(); +} + +ShaderParameterMap CShaderPresetGL::GetShaderParameters( + const std::vector& 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 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 auto& match : validParams) + { + // For each param found in the preset file + for (const auto& 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; +} + +bool CShaderPresetGL::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(target, dest); + + IShader* firstShader = m_pShaders.front().get(); + CShaderTextureGL* firstShaderTexture = m_pShaderTextures.front().get(); + IShader* lastShader = m_pShaders.back().get(); + int screenWidth = m_context.GetScreenWidth(); + int screenHeight = m_context.GetScreenHeight(); + + const unsigned passesNum = static_cast(m_pShaderTextures.size()); + + if (passesNum == 1) + firstShader->Render(source, target); + else if (passesNum == 2) + { + // Initialize FBO + firstShaderTexture->CreateFBO(screenWidth, screenHeight); + // Apply first pass + firstShaderTexture->BindFBO(); + RenderShader(firstShader, source, target); + firstShaderTexture->UnbindFBO(); + // Apply last pass + RenderShader(lastShader, firstShaderTexture, target); + } + else + { + // Initialize FBO + firstShaderTexture->CreateFBO(screenWidth, screenHeight); + // Apply first pass + firstShaderTexture->BindFBO(); + RenderShader(firstShader, source, target); + firstShaderTexture->UnbindFBO(); + + // Apply all passes except the first and last one (which needs to be applied to the backbuffer) + for (unsigned int shaderIdx = 1; shaderIdx < static_cast(m_pShaders.size()) - 1; + ++shaderIdx) + { + IShader* shader = m_pShaders[shaderIdx].get(); + CShaderTextureGL* prevTexture = m_pShaderTextures[shaderIdx - 1].get(); + CShaderTextureGL* texture = m_pShaderTextures[shaderIdx].get(); + texture->CreateFBO(screenWidth, screenHeight); + texture->BindFBO(); + RenderShader(shader, prevTexture, + target); // The target on each call is only used for setting the viewport + texture->UnbindFBO(); + } + + // TODO: Remove last texture, useless + // Apply last pass + CShaderTextureGL* secToLastTexture = m_pShaderTextures[m_pShaderTextures.size() - 2].get(); + RenderShader(lastShader, secToLastTexture, target); + } + + m_frameCount += static_cast(m_speed); + + // Restore our view port. + m_context.SetViewPort(viewPort); + + return true; +} + + +bool CShaderPresetGL::Update() +{ + auto updateFailed = [this](const std::string& msg) { + m_failedPaths.insert(m_presetPath); + auto message = "CShaderPresetGL::Update: " + msg + ". Disabling video shaders."; + CLog::Log(LOGWARNING, message.c_str()); + DisposeShaders(); + return false; + }; + + if (m_bPresetNeedsUpdate && !HasPathFailed(m_presetPath)) + { + DisposeShaders(); + + if (m_presetPath.empty()) + return false; + + if (!ReadPresetFile(m_presetPath)) + { + CLog::Log(LOGERROR, "%s - couldn't load shader preset %s or the shaders it references", + __func__, m_presetPath.c_str()); + return false; + } + + if (!CreateShaders()) + return updateFailed("Failed to initialize shaders"); + + if (!CreateBuffers()) + return updateFailed("Failed to initialize buffers"); + + if (!CreateShaderTextures()) + return updateFailed("A shader texture failed to init"); + } + + if (m_pShaders.empty()) + return false; + + // Each pass must have its own texture and the opposite is also true + if (m_pShaders.size() != m_pShaderTextures.size()) + return updateFailed("A shader or texture failed to init"); + + m_bPresetNeedsUpdate = false; + return true; +} + +void CShaderPresetGL::SetVideoSize(const unsigned videoWidth, const unsigned videoHeight) +{ + m_videoSize = {videoWidth, videoHeight}; + m_textureSize = CShaderUtils::GetOptimalTextureSize(m_videoSize); +} + +bool CShaderPresetGL::SetShaderPreset(const std::string& shaderPresetPath) +{ + m_bPresetNeedsUpdate = true; + m_presetPath = shaderPresetPath; + return Update(); +} + +const std::string& CShaderPresetGL::GetShaderPreset() const +{ + return m_presetPath; +} + +bool CShaderPresetGL::CreateShaderTextures() +{ + m_pShaderTextures.clear(); + + float2 prevSize = m_videoSize; + + unsigned int numPasses = static_cast(m_passes.size()); + + for (unsigned shaderIdx = 0; shaderIdx < numPasses; ++shaderIdx) + { + ShaderPass& pass = m_passes[shaderIdx]; + + // resolve final texture resolution, taking scale type and scale multiplier into account + float2 scaledSize; + switch (pass.fbo.scaleX.type) + { + case SCALE_TYPE_ABSOLUTE: + scaledSize.x = static_cast(pass.fbo.scaleX.abs); + break; + case SCALE_TYPE_VIEWPORT: + scaledSize.x = m_outputSize.x; + break; + case SCALE_TYPE_INPUT: + default: + scaledSize.x = prevSize.x; + break; + } + switch (pass.fbo.scaleY.type) + { + case SCALE_TYPE_ABSOLUTE: + scaledSize.y = static_cast(pass.fbo.scaleY.abs); + break; + case SCALE_TYPE_VIEWPORT: + scaledSize.y = m_outputSize.y; + break; + case SCALE_TYPE_INPUT: + default: + scaledSize.y = prevSize.y; + break; + } + + // if the scale was unspecified + if (pass.fbo.scaleX.scale == 0 && pass.fbo.scaleY.scale == 0) + { + // if the last shader has the scale unspecified + if (shaderIdx == numPasses - 1) + { + // we're supposed to output at full (viewport) res + scaledSize.x = m_outputSize.x; + scaledSize.y = m_outputSize.y; + } + } + else + { + scaledSize.x *= pass.fbo.scaleX.scale; + scaledSize.y *= pass.fbo.scaleY.scale; + } + + // Determine the framebuffer data format + unsigned int textureFormat; + if (pass.fbo.floatFramebuffer) + { + // Give priority to float framebuffer parameter (we can't use both float and sRGB) + textureFormat = GL_RGB32F; + } + else + { + if (pass.fbo.sRgbFramebuffer) + textureFormat = GL_SRGB8; + else + textureFormat = GL_RGBA; + } + + auto texture = new CGLTexture(static_cast(scaledSize.x), + static_cast(scaledSize.y), textureFormat); + + texture->CreateTextureObject(); + + if (texture->getMTexture() <= 0) + { + CLog::Log(LOGERROR, "Couldn't create a texture for video shader %s.", + pass.sourcePath.c_str()); + return false; + } + + glBindTexture(GL_TEXTURE_2D, texture->getMTexture()); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_NEVER); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_LOD, 0.0); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, MAX_FLOAT); + GLfloat blackBorder[4] = {0.0f, 0.0f, 0.0f, 0.0f}; + glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, blackBorder); + + m_pShaderTextures.emplace_back(new CShaderTextureGL(*texture)); + m_pShaders[shaderIdx]->SetSizes(prevSize, scaledSize); + + prevSize = scaledSize; + } + return true; +} + +bool CShaderPresetGL::CreateShaders() +{ + auto numPasses = m_passes.size(); + m_textureSize = CShaderUtils::GetOptimalTextureSize(m_videoSize); + + ShaderLutVec passLUTsGL; + for (unsigned shaderIdx = 0; shaderIdx < numPasses; ++shaderIdx) + { + const auto& pass = m_passes[shaderIdx]; + + for (unsigned i = 0; i < pass.luts.size(); ++i) + { + auto& lutStruct = pass.luts[i]; + + ShaderLutPtr passLut(new CShaderLutGL(lutStruct.strId, lutStruct.path)); + if (passLut->Create(m_context, lutStruct)) + passLUTsGL.emplace_back(std::move(passLut)); + } + + std::unique_ptr videoShader(new CShaderGL(m_context)); + + auto shaderSource = pass.vertexSource; //also contains fragment source + auto shaderPath = pass.sourcePath; + + // Get only the parameters belonging to this specific shader + ShaderParameterMap passParameters = GetShaderParameters(pass.parameters, pass.vertexSource); + + if (!videoShader->Create(shaderSource, shaderPath, passParameters, nullptr, passLUTsGL, + m_outputSize, pass.frameCountMod)) + { + CLog::Log(LOGERROR, "Couldn't create a video shader"); + return false; + } + m_pShaders.push_back(std::move(videoShader)); + } + return true; +} + +bool CShaderPresetGL::CreateBuffers() +{ + for (auto& videoShader : m_pShaders) + videoShader->CreateInputBuffer(); + + return true; +} + +void CShaderPresetGL::UpdateViewPort() +{ + CRect viewPort; + m_context.GetViewPort(viewPort); + UpdateViewPort(viewPort); +} + +void CShaderPresetGL::UpdateViewPort(CRect viewPort) +{ + float2 currentViewPortSize = {viewPort.Width(), viewPort.Height()}; + if (currentViewPortSize != m_outputSize) + { + m_outputSize = currentViewPortSize; + m_bPresetNeedsUpdate = true; + Update(); + } +} + +void CShaderPresetGL::UpdateMVPs() +{ + for (auto& videoShader : m_pShaders) + videoShader->UpdateMVP(); +} + +void CShaderPresetGL::DisposeShaders() +{ + m_pShaders.clear(); + m_pShaderTextures.clear(); + m_passes.clear(); + m_bPresetNeedsUpdate = true; +} + +void CShaderPresetGL::PrepareParameters(const IShaderTexture* texture, const CPoint* dest) +{ + for (unsigned shaderIdx = 0; shaderIdx < m_pShaders.size() - 1; ++shaderIdx) + { + auto& videoShader = m_pShaders[shaderIdx]; + videoShader->PrepareParameters(m_dest, false, static_cast(m_frameCount)); + } + + m_pShaders.back()->PrepareParameters(m_dest, true, static_cast(m_frameCount)); + + if (m_dest[0] != dest[0] || m_dest[1] != dest[1] || m_dest[2] != dest[2] || + m_dest[3] != dest[3] || texture->GetWidth() != m_outputSize.x || + texture->GetHeight() != m_outputSize.y) + { + for (size_t i = 0; i < 4; ++i) + m_dest[i] = dest[i]; + + m_outputSize = {texture->GetWidth(), texture->GetHeight()}; + + UpdateMVPs(); + UpdateViewPort(); + } +} + +void CShaderPresetGL::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); +} + +bool CShaderPresetGL::ReadPresetFile(const std::string& presetPath) +{ + return CServiceBroker::GetGameServices().VideoShaders().LoadPreset(presetPath, *this); +} + +void CShaderPresetGL::SetSpeed(double speed) +{ + m_speed = speed; +} + +ShaderPassVec& CShaderPresetGL::GetPasses() +{ + return m_passes; +} + +bool CShaderPresetGL::HasPathFailed(const std::string& path) const +{ + return m_failedPaths.find(path) != m_failedPaths.end(); +} diff --git a/xbmc/cores/RetroPlayer/shaders/gl/ShaderPresetGL.h b/xbmc/cores/RetroPlayer/shaders/gl/ShaderPresetGL.h new file mode 100644 index 0000000000000..66e647ffcc94e --- /dev/null +++ b/xbmc/cores/RetroPlayer/shaders/gl/ShaderPresetGL.h @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2019 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 "ShaderGL.h" +#include "ShaderTextureGL.h" +#include "cores/RetroPlayer/shaders/IShaderPreset.h" +#include "cores/RetroPlayer/shaders/ShaderTypes.h" +#include "games/GameServices.h" +#include "utils/Geometry.h" + +#include +#include +#include +#include + +#include "system_gl.h" + +namespace ADDON +{ +class CShaderPreset; +class CShaderPresetAddon; +} // namespace ADDON + +namespace KODI +{ +namespace RETRO +{ + +class CRenderContext; + +} + +namespace SHADER +{ + +class IShaderTexture; +class CShaderPresetGL : public IShaderPreset +{ +public: + // Instance of CShaderPreset + explicit CShaderPresetGL(RETRO::CRenderContext& context, + unsigned videoWidth = 0, + unsigned videoHeight = 0); + + ~CShaderPresetGL() 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; + void SetVideoSize(const unsigned videoWidth, const unsigned videoHeight) override; + bool SetShaderPreset(const std::string& shaderPresetPath) override; + const std::string& GetShaderPreset() const override; + ShaderPassVec& GetPasses() override; + bool Update(); + +private: + bool CreateShaderTextures(); + bool CreateShaders(); + bool CreateBuffers(); + void UpdateViewPort(); + void UpdateViewPort(CRect viewPort); + void UpdateMVPs(); + void DisposeShaders(); + void PrepareParameters(const IShaderTexture* texture, const CPoint dest[]); + void RenderShader(IShader* shader, IShaderTexture* source, IShaderTexture* target) const; + bool HasPathFailed(const std::string& path) 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; + + // Video shaders for the shader passes + std::vector> m_pShaders; + + // Intermediate textures used for pixel shader passes + std::vector> m_pShaderTextures; + + // First texture (this won't be needed when we have RGB rendering + //std::unique_ptr firstTexture; + + // Was the shader preset changed during the last frame? + bool m_bPresetNeedsUpdate = true; + + // Size of the viewport + float2 m_outputSize; + + // The size of the input texture itself + // Power-of-two sized. + float2 m_textureSize; + + // Size of the actual source video data (ie. 160x144 for the Game Boy) + float2 m_videoSize; + + // Number of frames that have passed + float m_frameCount = 0.0f; + + // Point/nearest neighbor sampler + //ID3D11SamplerState* m_pSampNearest = nullptr; + + // Linear sampler + //ID3D11SamplerState* m_pSampLinear = nullptr; + + // 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 m_failedPaths; + + // Array of vertices that comprise the full viewport + CPoint m_dest[4]; + + // Playback speed + double m_speed = 0.0; + + ShaderParameterMap GetShaderParameters(const std::vector& parameters, + const std::string& sourceStr) const; + + ShaderPassVec m_passes; +}; + +} // namespace SHADER +} // namespace KODI diff --git a/xbmc/cores/RetroPlayer/shaders/gl/ShaderTextureGL.cpp b/xbmc/cores/RetroPlayer/shaders/gl/ShaderTextureGL.cpp new file mode 100644 index 0000000000000..ff240eda6cbe5 --- /dev/null +++ b/xbmc/cores/RetroPlayer/shaders/gl/ShaderTextureGL.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2019 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 "ShaderTextureGL.h" + +#include "utils/log.h" + +using namespace KODI; +using namespace SHADER; + +bool CShaderTextureGL::CreateFBO(int width, int height) +{ + if (FBO == 0) + glGenFramebuffers(1, &FBO); + + GLuint renderTargetID = GetPointer()->getMTexture(); + if (renderTargetID == 0) + return false; + + BindFBO(); + glBindTexture(GL_TEXTURE_2D, renderTargetID); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, NULL); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, renderTargetID, 0); + + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) + { + CLog::Log(LOGERROR, "%s: Framebuffer is not complete!", __func__); + UnbindFBO(); + return false; + } + UnbindFBO(); + return true; +} + +void CShaderTextureGL::BindFBO() +{ + glBindFramebuffer(GL_FRAMEBUFFER, FBO); +} + +void CShaderTextureGL::UnbindFBO() +{ + glBindFramebuffer(GL_FRAMEBUFFER, 0); +} + +CShaderTextureGL::~CShaderTextureGL() +{ + if (FBO != 0) + glDeleteFramebuffers(1, &FBO); +} diff --git a/xbmc/cores/RetroPlayer/shaders/gl/ShaderTextureGL.h b/xbmc/cores/RetroPlayer/shaders/gl/ShaderTextureGL.h new file mode 100644 index 0000000000000..45f8904b1d87d --- /dev/null +++ b/xbmc/cores/RetroPlayer/shaders/gl/ShaderTextureGL.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2019 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 "guilib/Texture.h" +#include "guilib/TextureGL.h" + +namespace KODI +{ +namespace SHADER +{ +class CShaderTextureGL : public IShaderTexture +{ +public: + CShaderTextureGL() = default; + + CShaderTextureGL(CGLTexture* texture) : m_texture(texture) {} + CShaderTextureGL(CGLTexture& texture) : m_texture(&texture) {} + + // Destructor + // Don't delete texture since it wasn't created here + ~CShaderTextureGL() override; + + float GetWidth() const override { return static_cast(m_texture->GetWidth()); } + float GetHeight() const override { return static_cast(m_texture->GetHeight()); } + + CGLTexture* GetPointer() { return m_texture; } + bool CreateFBO(int width, int height); + void BindFBO(); + void UnbindFBO(); + +private: + CGLTexture* m_texture = nullptr; + GLuint FBO = 0; +}; +} // namespace SHADER +} // namespace KODI diff --git a/xbmc/cores/RetroPlayer/shaders/gl/ShaderTypesGL.h b/xbmc/cores/RetroPlayer/shaders/gl/ShaderTypesGL.h new file mode 100644 index 0000000000000..70a834b39d8ae --- /dev/null +++ b/xbmc/cores/RetroPlayer/shaders/gl/ShaderTypesGL.h @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2019 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 +#include + +namespace KODI +{ +namespace SHADER +{ + +class CShaderLutGL; +using ShaderLutPtrGL = std::shared_ptr; +using ShaderLutVecGL = std::vector; + +} // namespace SHADER +} // namespace KODI diff --git a/xbmc/cores/RetroPlayer/shaders/gl/ShaderUtilsGL.cpp b/xbmc/cores/RetroPlayer/shaders/gl/ShaderUtilsGL.cpp new file mode 100644 index 0000000000000..4442e085cc352 --- /dev/null +++ b/xbmc/cores/RetroPlayer/shaders/gl/ShaderUtilsGL.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2019 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 "ShaderUtilsGL.h" + +#include +#include + +using namespace KODI; +using namespace SHADER; + +GLint CShaderUtilsGL::TranslateWrapType(WRAP_TYPE wrap) +{ + GLint glWrap; + switch (wrap) + { + case WRAP_TYPE_EDGE: + glWrap = GL_CLAMP_TO_EDGE; + break; + case WRAP_TYPE_REPEAT: + glWrap = GL_REPEAT; + break; + case WRAP_TYPE_MIRRORED_REPEAT: + glWrap = GL_MIRRORED_REPEAT; + break; + case WRAP_TYPE_BORDER: + default: + glWrap = GL_CLAMP_TO_BORDER; + break; + } + + return glWrap; +} + +void CShaderUtilsGL::MoveVersionToFirstLine(std::string& source, + std::string& defineVertex, + std::string& defineFragment) +{ + std::istringstream str_stream(source); + source.clear(); + + std::string line; + bool firstLine = true; + while (std::getline(str_stream, line)) + { + if (!firstLine) + { + source += line; + } + else + { + defineVertex = line + "\n" + defineVertex; + defineFragment = line + "\n" + defineFragment; + firstLine = false; + } + } +} diff --git a/xbmc/cores/RetroPlayer/shaders/gl/ShaderUtilsGL.h b/xbmc/cores/RetroPlayer/shaders/gl/ShaderUtilsGL.h new file mode 100644 index 0000000000000..722fce2a0e188 --- /dev/null +++ b/xbmc/cores/RetroPlayer/shaders/gl/ShaderUtilsGL.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2019 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 "system_gl.h" + +namespace KODI +{ +namespace SHADER +{ + +class CShaderUtilsGL +{ +public: + static GLint TranslateWrapType(WRAP_TYPE wrap); + static void MoveVersionToFirstLine(std::string& source, + std::string& defineVertex, + std::string& defineFragment); +}; + +} // namespace SHADER +} // namespace KODI diff --git a/xbmc/cores/RetroPlayer/shaders/windows/ShaderDX.h b/xbmc/cores/RetroPlayer/shaders/windows/ShaderDX.h index 8b2ed96ab22b1..f66c0ca833bee 100644 --- a/xbmc/cores/RetroPlayer/shaders/windows/ShaderDX.h +++ b/xbmc/cores/RetroPlayer/shaders/windows/ShaderDX.h @@ -52,7 +52,14 @@ class CShaderDX : public CWinShader, public IShader // expose these from CWinShader bool CreateVertexBuffer(unsigned vertCount, unsigned vertSize) override; - bool CreateInputLayout(D3D11_INPUT_ELEMENT_DESC* layout, unsigned numElements) override; + + /*! + * \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 numElements); protected: void SetShaderParameters(CD3DTexture& sourceTexture); diff --git a/xbmc/cores/RetroPlayer/shaders/windows/ShaderPresetDX.h b/xbmc/cores/RetroPlayer/shaders/windows/ShaderPresetDX.h index 7aa3b36502c71..c236a11e7c9fb 100644 --- a/xbmc/cores/RetroPlayer/shaders/windows/ShaderPresetDX.h +++ b/xbmc/cores/RetroPlayer/shaders/windows/ShaderPresetDX.h @@ -82,7 +82,7 @@ class CShaderPresetDX : public IShaderPreset std::string m_presetPath; // Video shaders for the shader passes - std::vector> m_pShaders; + std::vector> m_pShaders; // Intermediate textures used for pixel shader passes std::vector> m_pShaderTextures; diff --git a/xbmc/games/dialogs/osd/DialogGameVideoFilter.cpp b/xbmc/games/dialogs/osd/DialogGameVideoFilter.cpp index cab2ee39fc90d..6e311bb6982cb 100644 --- a/xbmc/games/dialogs/osd/DialogGameVideoFilter.cpp +++ b/xbmc/games/dialogs/osd/DialogGameVideoFilter.cpp @@ -101,9 +101,16 @@ void CDialogGameVideoFilter::InitVideoFilters() { std::vector videoFilters; + std::string xmlFilename; +#ifdef TARGET_WINDOWS + xmlFilename = "ShaderPresetsHLSLP.xml"; +#else + xmlFilename = "ShaderPresetsGLSLP.xml"; +#endif + // TODO: Have the add-on give us the xml as a string (or parse it) static const std::string addonPath = std::string("special://xbmcbinaddons/") + PRESETS_ADDON_NAME; - static const std::string xmlPath = "special://xbmc/system/shaders/presets/shader-manifest.xml"; + static const std::string xmlPath = addonPath + "/resources/" + xmlFilename; std::string basePath = URIUtils::GetBasePath(xmlPath); CXBMCTinyXML xml = CXBMCTinyXML(xmlPath); diff --git a/xbmc/guilib/TextureGL.cpp b/xbmc/guilib/TextureGL.cpp index 86a3fb80b07a6..d231cf0516000 100644 --- a/xbmc/guilib/TextureGL.cpp +++ b/xbmc/guilib/TextureGL.cpp @@ -21,13 +21,18 @@ CTexture* CTexture::CreateTexture(unsigned int width, unsigned int height, unsig return new CGLTexture(width, height, format); } -CGLTexture::CGLTexture(unsigned int width, unsigned int height, unsigned int format) - : CTexture(width, height, format) +/************************************************************************/ +/* CGLTexture */ +/************************************************************************/ +CGLTexture::CGLTexture(unsigned int width, unsigned int height, unsigned int format, GLuint texture) +: CTexture(width, height, format) { unsigned int major, minor; CServiceBroker::GetRenderSystem()->GetRenderVersion(major, minor); if (major >= 3) m_isOglVersion3orNewer = true; + + m_texture = texture; } CGLTexture::~CGLTexture() @@ -216,3 +221,8 @@ void CGLTexture::BindToUnit(unsigned int unit) glBindTexture(GL_TEXTURE_2D, m_texture); } +GLuint CGLTexture::getMTexture() const +{ + return m_texture; +} + diff --git a/xbmc/guilib/TextureGL.h b/xbmc/guilib/TextureGL.h index 51c7035de6ced..8dae2ca5d12f6 100644 --- a/xbmc/guilib/TextureGL.h +++ b/xbmc/guilib/TextureGL.h @@ -18,7 +18,7 @@ class CGLTexture : public CTexture { public: - CGLTexture(unsigned int width = 0, unsigned int height = 0, unsigned int format = XB_FMT_A8R8G8B8); + CGLTexture(unsigned int width = 0, unsigned int height = 0, unsigned int format = XB_FMT_A8R8G8B8, GLuint texture = 0); ~CGLTexture() override; void CreateTextureObject() override; @@ -26,6 +26,8 @@ class CGLTexture : public CTexture void LoadToGPU() override; void BindToUnit(unsigned int unit) override; + GLuint getMTexture() const; + protected: GLuint m_texture = 0; bool m_isOglVersion3orNewer = false;