diff options
Diffstat (limited to 'xbmc/rendering')
30 files changed, 5805 insertions, 0 deletions
diff --git a/xbmc/rendering/CMakeLists.txt b/xbmc/rendering/CMakeLists.txt new file mode 100644 index 0000000..c212a96 --- /dev/null +++ b/xbmc/rendering/CMakeLists.txt @@ -0,0 +1,27 @@ +set(SOURCES RenderSystem.cpp) + +set(HEADERS RenderSystem.h + RenderSystemTypes.h) + +if(OPENGL_FOUND OR OPENGLES_FOUND) + list(APPEND SOURCES MatrixGL.cpp) + list(APPEND HEADERS MatrixGL.h) + + if(ARCH MATCHES arm AND ENABLE_NEON) + list(APPEND SOURCES MatrixGL.neon.cpp) + if(NOT DEFINED NEON_FLAGS) + set_source_files_properties(MatrixGL.neon.cpp PROPERTIES COMPILE_OPTIONS -mfpu=neon) + endif() + endif() +endif() + +core_add_library(rendering) +if(NOT CORE_SYSTEM_NAME STREQUAL windows AND NOT CORE_SYSTEM_NAME STREQUAL windowsstore) + if(HAVE_SSE) + target_compile_options(${CORE_LIBRARY} PRIVATE -msse) + endif() + if(HAVE_SSE2) + target_compile_options(${CORE_LIBRARY} PRIVATE -msse2) + endif() +endif() + diff --git a/xbmc/rendering/MatrixGL.cpp b/xbmc/rendering/MatrixGL.cpp new file mode 100644 index 0000000..6e90383 --- /dev/null +++ b/xbmc/rendering/MatrixGL.cpp @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2005-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 "MatrixGL.h" + +#include "ServiceBroker.h" +#include "utils/TransformMatrix.h" + +#if defined(HAS_NEON) && !defined(__LP64__) +#include "utils/CPUInfo.h" +void Matrix4Mul(float* src_mat_1, const float* src_mat_2); +#endif + +#include <cmath> + +CMatrixGLStack glMatrixModview = CMatrixGLStack(); +CMatrixGLStack glMatrixProject = CMatrixGLStack(); +CMatrixGLStack glMatrixTexture = CMatrixGLStack(); + +CMatrixGL::CMatrixGL(const TransformMatrix &src) noexcept +{ + for(int i = 0; i < 3; i++) + for(int j = 0; j < 4; j++) + m_pMatrix[j * 4 + i] = src.m[i][j]; + + m_pMatrix[3] = 0.0f; + m_pMatrix[7] = 0.0f; + m_pMatrix[11] = 0.0f; + m_pMatrix[15] = 1.0f; +} + +void CMatrixGL::LoadIdentity() +{ + m_pMatrix[0] = 1.0f; m_pMatrix[4] = 0.0f; m_pMatrix[8] = 0.0f; m_pMatrix[12] = 0.0f; + m_pMatrix[1] = 0.0f; m_pMatrix[5] = 1.0f; m_pMatrix[9] = 0.0f; m_pMatrix[13] = 0.0f; + m_pMatrix[2] = 0.0f; m_pMatrix[6] = 0.0f; m_pMatrix[10] = 1.0f; m_pMatrix[14] = 0.0f; + m_pMatrix[3] = 0.0f; m_pMatrix[7] = 0.0f; m_pMatrix[11] = 0.0f; m_pMatrix[15] = 1.0f; +} + +void CMatrixGL::Ortho(GLfloat l, GLfloat r, GLfloat b, GLfloat t, GLfloat n, GLfloat f) +{ + GLfloat u = 2.0f / (r - l); + GLfloat v = 2.0f / (t - b); + GLfloat w = -2.0f / (f - n); + GLfloat x = - (r + l) / (r - l); + GLfloat y = - (t + b) / (t - b); + GLfloat z = - (f + n) / (f - n); + const CMatrixGL matrix{ u, 0.0f, 0.0f, 0.0f, + 0.0f, v, 0.0f, 0.0f, + 0.0f, 0.0f, w, 0.0f, + x, y, z, 1.0f}; + MultMatrixf(matrix); +} + +void CMatrixGL::Ortho2D(GLfloat l, GLfloat r, GLfloat b, GLfloat t) +{ + GLfloat u = 2.0f / (r - l); + GLfloat v = 2.0f / (t - b); + GLfloat x = - (r + l) / (r - l); + GLfloat y = - (t + b) / (t - b); + const CMatrixGL matrix{ u, 0.0f, 0.0f, 0.0f, + 0.0f, v, 0.0f, 0.0f, + 0.0f, 0.0f,-1.0f, 0.0f, + x, y, 0.0f, 1.0f}; + MultMatrixf(matrix); +} + +void CMatrixGL::Frustum(GLfloat l, GLfloat r, GLfloat b, GLfloat t, GLfloat n, GLfloat f) +{ + GLfloat u = (2.0f * n) / (r - l); + GLfloat v = (2.0f * n) / (t - b); + GLfloat w = (r + l) / (r - l); + GLfloat x = (t + b) / (t - b); + GLfloat y = - (f + n) / (f - n); + GLfloat z = - (2.0f * f * n) / (f - n); + const CMatrixGL matrix{ u, 0.0f, 0.0f, 0.0f, + 0.0f, v, 0.0f, 0.0f, + w, x, y,-1.0f, + 0.0f, 0.0f, z, 0.0f}; + MultMatrixf(matrix); +} + +void CMatrixGL::Translatef(GLfloat x, GLfloat y, GLfloat z) +{ + const CMatrixGL matrix{1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + x, y, z, 1.0f}; + MultMatrixf(matrix); +} + +void CMatrixGL::Scalef(GLfloat x, GLfloat y, GLfloat z) +{ + const CMatrixGL matrix{ x, 0.0f, 0.0f, 0.0f, + 0.0f, y, 0.0f, 0.0f, + 0.0f, 0.0f, z, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f}; + MultMatrixf(matrix); +} + +void CMatrixGL::Rotatef(GLfloat angle, GLfloat x, GLfloat y, GLfloat z) +{ + GLfloat modulus = std::sqrt((x*x)+(y*y)+(z*z)); + if (modulus != 0.0f) + { + x /= modulus; + y /= modulus; + z /= modulus; + } + GLfloat cosine = std::cos(angle); + GLfloat sine = std::sin(angle); + GLfloat cos1 = 1 - cosine; + GLfloat a = (x*x*cos1) + cosine; + GLfloat b = (x*y*cos1) - (z*sine); + GLfloat c = (x*z*cos1) + (y*sine); + GLfloat d = (y*x*cos1) + (z*sine); + GLfloat e = (y*y*cos1) + cosine; + GLfloat f = (y*z*cos1) - (x*sine); + GLfloat g = (z*x*cos1) - (y*sine); + GLfloat h = (z*y*cos1) + (x*sine); + GLfloat i = (z*z*cos1) + cosine; + const CMatrixGL matrix{ a, d, g, 0.0f, + b, e, h, 0.0f, + c, f, i, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f}; + MultMatrixf(matrix); +} + +void CMatrixGL::MultMatrixf(const CMatrixGL &matrix) noexcept +{ +#if defined(HAS_NEON) && !defined(__LP64__) + if ((CServiceBroker::GetCPUInfo()->GetCPUFeatures() & CPU_FEATURE_NEON) == CPU_FEATURE_NEON) + { + Matrix4Mul(m_pMatrix, matrix.m_pMatrix); + return; + } +#endif + GLfloat a = (matrix.m_pMatrix[0] * m_pMatrix[0]) + (matrix.m_pMatrix[1] * m_pMatrix[4]) + (matrix.m_pMatrix[2] * m_pMatrix[8]) + (matrix.m_pMatrix[3] * m_pMatrix[12]); + GLfloat b = (matrix.m_pMatrix[0] * m_pMatrix[1]) + (matrix.m_pMatrix[1] * m_pMatrix[5]) + (matrix.m_pMatrix[2] * m_pMatrix[9]) + (matrix.m_pMatrix[3] * m_pMatrix[13]); + GLfloat c = (matrix.m_pMatrix[0] * m_pMatrix[2]) + (matrix.m_pMatrix[1] * m_pMatrix[6]) + (matrix.m_pMatrix[2] * m_pMatrix[10]) + (matrix.m_pMatrix[3] * m_pMatrix[14]); + GLfloat d = (matrix.m_pMatrix[0] * m_pMatrix[3]) + (matrix.m_pMatrix[1] * m_pMatrix[7]) + (matrix.m_pMatrix[2] * m_pMatrix[11]) + (matrix.m_pMatrix[3] * m_pMatrix[15]); + GLfloat e = (matrix.m_pMatrix[4] * m_pMatrix[0]) + (matrix.m_pMatrix[5] * m_pMatrix[4]) + (matrix.m_pMatrix[6] * m_pMatrix[8]) + (matrix.m_pMatrix[7] * m_pMatrix[12]); + GLfloat f = (matrix.m_pMatrix[4] * m_pMatrix[1]) + (matrix.m_pMatrix[5] * m_pMatrix[5]) + (matrix.m_pMatrix[6] * m_pMatrix[9]) + (matrix.m_pMatrix[7] * m_pMatrix[13]); + GLfloat g = (matrix.m_pMatrix[4] * m_pMatrix[2]) + (matrix.m_pMatrix[5] * m_pMatrix[6]) + (matrix.m_pMatrix[6] * m_pMatrix[10]) + (matrix.m_pMatrix[7] * m_pMatrix[14]); + GLfloat h = (matrix.m_pMatrix[4] * m_pMatrix[3]) + (matrix.m_pMatrix[5] * m_pMatrix[7]) + (matrix.m_pMatrix[6] * m_pMatrix[11]) + (matrix.m_pMatrix[7] * m_pMatrix[15]); + GLfloat i = (matrix.m_pMatrix[8] * m_pMatrix[0]) + (matrix.m_pMatrix[9] * m_pMatrix[4]) + (matrix.m_pMatrix[10] * m_pMatrix[8]) + (matrix.m_pMatrix[11] * m_pMatrix[12]); + GLfloat j = (matrix.m_pMatrix[8] * m_pMatrix[1]) + (matrix.m_pMatrix[9] * m_pMatrix[5]) + (matrix.m_pMatrix[10] * m_pMatrix[9]) + (matrix.m_pMatrix[11] * m_pMatrix[13]); + GLfloat k = (matrix.m_pMatrix[8] * m_pMatrix[2]) + (matrix.m_pMatrix[9] * m_pMatrix[6]) + (matrix.m_pMatrix[10] * m_pMatrix[10]) + (matrix.m_pMatrix[11] * m_pMatrix[14]); + GLfloat l = (matrix.m_pMatrix[8] * m_pMatrix[3]) + (matrix.m_pMatrix[9] * m_pMatrix[7]) + (matrix.m_pMatrix[10] * m_pMatrix[11]) + (matrix.m_pMatrix[11] * m_pMatrix[15]); + GLfloat m = (matrix.m_pMatrix[12] * m_pMatrix[0]) + (matrix.m_pMatrix[13] * m_pMatrix[4]) + (matrix.m_pMatrix[14] * m_pMatrix[8]) + (matrix.m_pMatrix[15] * m_pMatrix[12]); + GLfloat n = (matrix.m_pMatrix[12] * m_pMatrix[1]) + (matrix.m_pMatrix[13] * m_pMatrix[5]) + (matrix.m_pMatrix[14] * m_pMatrix[9]) + (matrix.m_pMatrix[15] * m_pMatrix[13]); + GLfloat o = (matrix.m_pMatrix[12] * m_pMatrix[2]) + (matrix.m_pMatrix[13] * m_pMatrix[6]) + (matrix.m_pMatrix[14] * m_pMatrix[10]) + (matrix.m_pMatrix[15] * m_pMatrix[14]); + GLfloat p = (matrix.m_pMatrix[12] * m_pMatrix[3]) + (matrix.m_pMatrix[13] * m_pMatrix[7]) + (matrix.m_pMatrix[14] * m_pMatrix[11]) + (matrix.m_pMatrix[15] * m_pMatrix[15]); + m_pMatrix[0] = a; m_pMatrix[4] = e; m_pMatrix[8] = i; m_pMatrix[12] = m; + m_pMatrix[1] = b; m_pMatrix[5] = f; m_pMatrix[9] = j; m_pMatrix[13] = n; + m_pMatrix[2] = c; m_pMatrix[6] = g; m_pMatrix[10] = k; m_pMatrix[14] = o; + m_pMatrix[3] = d; m_pMatrix[7] = h; m_pMatrix[11] = l; m_pMatrix[15] = p; +} + +// gluLookAt implementation taken from Mesa3D +void CMatrixGL::LookAt(GLfloat eyex, GLfloat eyey, GLfloat eyez, GLfloat centerx, GLfloat centery, GLfloat centerz, GLfloat upx, GLfloat upy, GLfloat upz) +{ + GLfloat forward[3], side[3], up[3]; + + forward[0] = centerx - eyex; + forward[1] = centery - eyey; + forward[2] = centerz - eyez; + + up[0] = upx; + up[1] = upy; + up[2] = upz; + + GLfloat tmp = std::sqrt(forward[0]*forward[0] + forward[1]*forward[1] + forward[2]*forward[2]); + if (tmp != 0.0f) + { + forward[0] /= tmp; + forward[1] /= tmp; + forward[2] /= tmp; + } + + side[0] = forward[1]*up[2] - forward[2]*up[1]; + side[1] = forward[2]*up[0] - forward[0]*up[2]; + side[2] = forward[0]*up[1] - forward[1]*up[0]; + + tmp = std::sqrt(side[0]*side[0] + side[1]*side[1] + side[2]*side[2]); + if (tmp != 0.0f) + { + side[0] /= tmp; + side[1] /= tmp; + side[2] /= tmp; + } + + up[0] = side[1]*forward[2] - side[2]*forward[1]; + up[1] = side[2]*forward[0] - side[0]*forward[2]; + up[2] = side[0]*forward[1] - side[1]*forward[0]; + + const CMatrixGL matrix{ + side[0], up[0], -forward[0], 0.0f, + side[1], up[1], -forward[1], 0.0f, + side[2], up[2], -forward[2], 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f, + }; + + MultMatrixf(matrix); + Translatef(-eyex, -eyey, -eyez); +} + +static void __gluMultMatrixVecf(const GLfloat matrix[16], const GLfloat in[4], GLfloat out[4]) +{ + int i; + + for (i=0; i<4; i++) + { + out[i] = in[0] * matrix[0*4+i] + + in[1] * matrix[1*4+i] + + in[2] * matrix[2*4+i] + + in[3] * matrix[3*4+i]; + } +} + +// gluProject implementation taken from Mesa3D +bool CMatrixGL::Project(GLfloat objx, GLfloat objy, GLfloat objz, const GLfloat modelMatrix[16], const GLfloat projMatrix[16], const GLint viewport[4], GLfloat* winx, GLfloat* winy, GLfloat* winz) +{ + GLfloat in[4]; + GLfloat out[4]; + + in[0]=objx; + in[1]=objy; + in[2]=objz; + in[3]=1.0; + __gluMultMatrixVecf(modelMatrix, in, out); + __gluMultMatrixVecf(projMatrix, out, in); + if (in[3] == 0.0f) + return false; + in[0] /= in[3]; + in[1] /= in[3]; + in[2] /= in[3]; + /* Map x, y and z to range 0-1 */ + in[0] = in[0] * 0.5f + 0.5f; + in[1] = in[1] * 0.5f + 0.5f; + in[2] = in[2] * 0.5f + 0.5f; + + /* Map x,y to viewport */ + in[0] = in[0] * viewport[2] + viewport[0]; + in[1] = in[1] * viewport[3] + viewport[1]; + + *winx=in[0]; + *winy=in[1]; + *winz=in[2]; + return true; +} + +void CMatrixGLStack::Load() +{ + +} diff --git a/xbmc/rendering/MatrixGL.h b/xbmc/rendering/MatrixGL.h new file mode 100644 index 0000000..333da2f --- /dev/null +++ b/xbmc/rendering/MatrixGL.h @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2005-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 <stack> + +#include "system_gl.h" + +class TransformMatrix; + +class CMatrixGL +{ +public: + CMatrixGL() = default; + + constexpr CMatrixGL(GLfloat x0, GLfloat x1, GLfloat x2, GLfloat x3, + GLfloat x4, GLfloat x5, GLfloat x6, GLfloat x7, + GLfloat x8, GLfloat x9, GLfloat x10, GLfloat x11, + GLfloat x12, GLfloat x13, GLfloat x14, GLfloat x15) + :m_pMatrix{x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15} {} + + CMatrixGL(const TransformMatrix &src) noexcept; + + operator const float*() const { return m_pMatrix; } + + void LoadIdentity(); + void Ortho(GLfloat l, GLfloat r, GLfloat b, GLfloat t, GLfloat n, GLfloat f); + void Ortho2D(GLfloat l, GLfloat r, GLfloat b, GLfloat t); + void Frustum(GLfloat l, GLfloat r, GLfloat b, GLfloat t, GLfloat n, GLfloat f); + void Translatef(GLfloat x, GLfloat y, GLfloat z); + void Scalef(GLfloat x, GLfloat y, GLfloat z); + void Rotatef(GLfloat angle, GLfloat x, GLfloat y, GLfloat z); + void MultMatrixf(const CMatrixGL &matrix) noexcept; + void LookAt(GLfloat eyex, GLfloat eyey, GLfloat eyez, GLfloat centerx, GLfloat centery, GLfloat centerz, GLfloat upx, GLfloat upy, GLfloat upz); + + static bool Project(GLfloat objx, GLfloat objy, GLfloat objz, const GLfloat modelMatrix[16], const GLfloat projMatrix[16], const GLint viewport[4], GLfloat* winx, GLfloat* winy, GLfloat* winz); + +private: + /* alignas(16) allows better SIMD optimizations (e.g. SSE2 benefits + a lot from this) */ + alignas(16) GLfloat m_pMatrix[16]; +}; + +class CMatrixGLStack +{ +public: + void Push() + { + m_stack.push(m_current); + } + + void Clear() + { + m_stack = std::stack<CMatrixGL>(); + } + + void Pop() + { + if(!m_stack.empty()) + { + m_current = m_stack.top(); + m_stack.pop(); + } + } + + void Load(); + void PopLoad() { Pop(); Load(); } + + CMatrixGL& Get() { return m_current; } + CMatrixGL* operator->() { return &m_current; } + +private: + std::stack<CMatrixGL> m_stack; + CMatrixGL m_current; +}; + +extern CMatrixGLStack glMatrixModview; +extern CMatrixGLStack glMatrixProject; +extern CMatrixGLStack glMatrixTexture; diff --git a/xbmc/rendering/MatrixGL.neon.cpp b/xbmc/rendering/MatrixGL.neon.cpp new file mode 100644 index 0000000..3f3f33c --- /dev/null +++ b/xbmc/rendering/MatrixGL.neon.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2005-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. + */ + + +void Matrix4Mul(float* src_mat_1, const float* src_mat_2) +{ + asm volatile ( + // Store A & B leaving room at top of registers for result (q0-q3) + "vldmia %0, { q4-q7 } \n\t" + "vldmia %1, { q8-q11 } \n\t" + + // result = first column of B x first row of A + "vmul.f32 q0, q8, d8[0]\n\t" + "vmul.f32 q1, q8, d10[0]\n\t" + "vmul.f32 q2, q8, d12[0]\n\t" + "vmul.f32 q3, q8, d14[0]\n\t" + + // result += second column of B x second row of A + "vmla.f32 q0, q9, d8[1]\n\t" + "vmla.f32 q1, q9, d10[1]\n\t" + "vmla.f32 q2, q9, d12[1]\n\t" + "vmla.f32 q3, q9, d14[1]\n\t" + + // result += third column of B x third row of A + "vmla.f32 q0, q10, d9[0]\n\t" + "vmla.f32 q1, q10, d11[0]\n\t" + "vmla.f32 q2, q10, d13[0]\n\t" + "vmla.f32 q3, q10, d15[0]\n\t" + + // result += last column of B x last row of A + "vmla.f32 q0, q11, d9[1]\n\t" + "vmla.f32 q1, q11, d11[1]\n\t" + "vmla.f32 q2, q11, d13[1]\n\t" + "vmla.f32 q3, q11, d15[1]\n\t" + + // output = result registers + "vstmia %1, { q0-q3 }" + : //no output + : "r" (src_mat_2), "r" (src_mat_1) // input - note *value* of pointer doesn't change + : "memory", "q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7", "q8", "q9", "q10", "q11" //clobber + ); +} diff --git a/xbmc/rendering/RenderSystem.cpp b/xbmc/rendering/RenderSystem.cpp new file mode 100644 index 0000000..3f10562 --- /dev/null +++ b/xbmc/rendering/RenderSystem.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2005-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 "RenderSystem.h" + +#include "Util.h" +#include "guilib/GUIFontManager.h" +#include "guilib/GUIImage.h" +#include "guilib/GUILabelControl.h" +#include "settings/AdvancedSettings.h" +#include "settings/SettingsComponent.h" + +CRenderSystemBase::CRenderSystemBase() +{ + m_bRenderCreated = false; + m_bVSync = true; + m_maxTextureSize = 2048; + m_RenderVersionMajor = 0; + m_RenderVersionMinor = 0; + m_minDXTPitch = 0; +} + +CRenderSystemBase::~CRenderSystemBase() = default; + +void CRenderSystemBase::GetRenderVersion(unsigned int& major, unsigned int& minor) const +{ + major = m_RenderVersionMajor; + minor = m_RenderVersionMinor; +} + +bool CRenderSystemBase::SupportsNPOT(bool dxt) const +{ + if (dxt) + return false; + + return true; +} + +bool CRenderSystemBase::SupportsStereo(RENDER_STEREO_MODE mode) const +{ + switch(mode) + { + case RENDER_STEREO_MODE_OFF: + case RENDER_STEREO_MODE_SPLIT_HORIZONTAL: + case RENDER_STEREO_MODE_SPLIT_VERTICAL: + case RENDER_STEREO_MODE_MONO: + return true; + default: + return false; + } +} + +void CRenderSystemBase::ShowSplash(const std::string& message) +{ + if (!CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_splashImage && !(m_splashImage || !message.empty())) + return; + + if (!m_splashImage) + { + m_splashImage = std::unique_ptr<CGUIImage>(new CGUIImage(0, 0, 0, 0, CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth(), + CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight(), CTextureInfo(CUtil::GetSplashPath()))); + m_splashImage->SetAspectRatio(CAspectRatio::AR_SCALE); + } + + CServiceBroker::GetWinSystem()->GetGfxContext().lock(); + CServiceBroker::GetWinSystem()->GetGfxContext().Clear(); + + RESOLUTION_INFO res = CServiceBroker::GetWinSystem()->GetGfxContext().GetResInfo(); + CServiceBroker::GetWinSystem()->GetGfxContext().SetRenderingResolution(res, true); + + //render splash image + BeginRender(); + + m_splashImage->AllocResources(); + m_splashImage->Render(); + m_splashImage->FreeResources(); + + if (!message.empty()) + { + if (!m_splashMessageLayout) + { + auto messageFont = g_fontManager.LoadTTF("__splash__", "arial.ttf", 0xFFFFFFFF, 0, 20, FONT_STYLE_NORMAL, false, 1.0f, 1.0f, &res); + if (messageFont) + m_splashMessageLayout = std::unique_ptr<CGUITextLayout>(new CGUITextLayout(messageFont, true, 0)); + } + + if (m_splashMessageLayout) + { + m_splashMessageLayout->Update(message, 1150, false, true); + float textWidth, textHeight; + m_splashMessageLayout->GetTextExtent(textWidth, textHeight); + + int width = CServiceBroker::GetWinSystem()->GetGfxContext().GetWidth(); + int height = CServiceBroker::GetWinSystem()->GetGfxContext().GetHeight(); + float y = height - textHeight - 100; + m_splashMessageLayout->RenderOutline(width/2, y, 0, 0xFF000000, XBFONT_CENTER_X, width); + } + } + + //show it on screen + EndRender(); + CServiceBroker::GetWinSystem()->GetGfxContext().unlock(); + CServiceBroker::GetWinSystem()->GetGfxContext().Flip(true, false); +} + diff --git a/xbmc/rendering/RenderSystem.h b/xbmc/rendering/RenderSystem.h new file mode 100644 index 0000000..0c3d6e0 --- /dev/null +++ b/xbmc/rendering/RenderSystem.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2005-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 "RenderSystemTypes.h" +#include "utils/ColorUtils.h" +#include "utils/Geometry.h" + +#include <memory> +#include <string> + +/* + * CRenderSystemBase interface allows us to create the rendering engine we use. + * We currently have two engines: OpenGL and DirectX + * This interface is very basic since a lot of the actual details will go in to the derived classes + */ + +class CGUIImage; +class CGUITextLayout; + +class CRenderSystemBase +{ +public: + CRenderSystemBase(); + virtual ~CRenderSystemBase(); + + virtual bool InitRenderSystem() = 0; + virtual bool DestroyRenderSystem() = 0; + virtual bool ResetRenderSystem(int width, int height) = 0; + + virtual bool BeginRender() = 0; + virtual bool EndRender() = 0; + virtual void PresentRender(bool rendered, bool videoLayer) = 0; + virtual bool ClearBuffers(UTILS::COLOR::Color color) = 0; + virtual bool IsExtSupported(const char* extension) const = 0; + + virtual void SetViewPort(const CRect& viewPort) = 0; + virtual void GetViewPort(CRect& viewPort) = 0; + virtual void RestoreViewPort() {} + + virtual bool ScissorsCanEffectClipping() { return false; } + virtual CRect ClipRectToScissorRect(const CRect &rect) { return CRect(); } + virtual void SetScissors(const CRect &rect) = 0; + virtual void ResetScissors() = 0; + + virtual void CaptureStateBlock() = 0; + virtual void ApplyStateBlock() = 0; + + virtual void SetCameraPosition(const CPoint &camera, int screenWidth, int screenHeight, float stereoFactor = 0.f) = 0; + virtual void SetStereoMode(RENDER_STEREO_MODE mode, RENDER_STEREO_VIEW view) + { + m_stereoMode = mode; + m_stereoView = view; + } + + /** + * Project (x,y,z) 3d scene coordinates to (x,y) 2d screen coordinates + */ + virtual void Project(float &x, float &y, float &z) { } + + virtual std::string GetShaderPath(const std::string &filename) { return ""; } + + void GetRenderVersion(unsigned int& major, unsigned int& minor) const; + const std::string& GetRenderVendor() const { return m_RenderVendor; } + const std::string& GetRenderRenderer() const { return m_RenderRenderer; } + const std::string& GetRenderVersionString() const { return m_RenderVersion; } + virtual bool SupportsNPOT(bool dxt) const; + virtual bool SupportsStereo(RENDER_STEREO_MODE mode) const; + unsigned int GetMaxTextureSize() const { return m_maxTextureSize; } + unsigned int GetMinDXTPitch() const { return m_minDXTPitch; } + + virtual void ShowSplash(const std::string& message); + +protected: + bool m_bRenderCreated; + bool m_bVSync; + unsigned int m_maxTextureSize; + unsigned int m_minDXTPitch; + + std::string m_RenderRenderer; + std::string m_RenderVendor; + std::string m_RenderVersion; + int m_RenderVersionMinor; + int m_RenderVersionMajor; + RENDER_STEREO_VIEW m_stereoView = RENDER_STEREO_VIEW_OFF; + RENDER_STEREO_MODE m_stereoMode = RENDER_STEREO_MODE_OFF; + bool m_limitedColorRange = false; + + std::unique_ptr<CGUIImage> m_splashImage; + std::unique_ptr<CGUITextLayout> m_splashMessageLayout; +}; + diff --git a/xbmc/rendering/RenderSystemTypes.h b/xbmc/rendering/RenderSystemTypes.h new file mode 100644 index 0000000..c818183 --- /dev/null +++ b/xbmc/rendering/RenderSystemTypes.h @@ -0,0 +1,35 @@ +/* + * 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 + +enum RENDER_STEREO_VIEW +{ + RENDER_STEREO_VIEW_OFF, + RENDER_STEREO_VIEW_LEFT, + RENDER_STEREO_VIEW_RIGHT, +}; + +enum RENDER_STEREO_MODE +{ + RENDER_STEREO_MODE_OFF, + RENDER_STEREO_MODE_SPLIT_HORIZONTAL, + RENDER_STEREO_MODE_SPLIT_VERTICAL, + RENDER_STEREO_MODE_ANAGLYPH_RED_CYAN, + RENDER_STEREO_MODE_ANAGLYPH_GREEN_MAGENTA, + RENDER_STEREO_MODE_ANAGLYPH_YELLOW_BLUE, + RENDER_STEREO_MODE_INTERLACED, + RENDER_STEREO_MODE_CHECKERBOARD, + RENDER_STEREO_MODE_HARDWAREBASED, + RENDER_STEREO_MODE_MONO, + RENDER_STEREO_MODE_COUNT, + + // Pseudo modes + RENDER_STEREO_MODE_AUTO = 100, + RENDER_STEREO_MODE_UNDEFINED = 999, +}; diff --git a/xbmc/rendering/dx/CMakeLists.txt b/xbmc/rendering/dx/CMakeLists.txt new file mode 100644 index 0000000..7466907 --- /dev/null +++ b/xbmc/rendering/dx/CMakeLists.txt @@ -0,0 +1,11 @@ +set(SOURCES DeviceResources.cpp + RenderSystemDX.cpp + ScreenshotSurfaceWindows.cpp) + +set(HEADERS DeviceResources.h + DirectXHelper.h + RenderContext.h + RenderSystemDX.h + ScreenshotSurfaceWindows.h) + +core_add_library(rendering_dx) diff --git a/xbmc/rendering/dx/DeviceResources.cpp b/xbmc/rendering/dx/DeviceResources.cpp new file mode 100644 index 0000000..b3ae890 --- /dev/null +++ b/xbmc/rendering/dx/DeviceResources.cpp @@ -0,0 +1,1361 @@ +/* + * Copyright (C) 2005-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 "DeviceResources.h" + +#include "DirectXHelper.h" +#include "RenderContext.h" +#include "ServiceBroker.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "messaging/ApplicationMessenger.h" +#include "settings/Settings.h" +#include "settings/SettingsComponent.h" +#include "utils/SystemInfo.h" +#include "utils/log.h" +#include "windowing/GraphicContext.h" + +#include "platform/win32/CharsetConverter.h" +#include "platform/win32/WIN32Util.h" + +#ifdef TARGET_WINDOWS_STORE +#include <winrt/Windows.Graphics.Display.Core.h> + +extern "C" +{ +#include <libavutil/rational.h> +} +#endif + +#ifdef _DEBUG +#include <dxgidebug.h> +#pragma comment(lib, "dxgi.lib") +#endif // _DEBUG + +using namespace DirectX; +using namespace Microsoft::WRL; +using namespace Concurrency; +namespace winrt +{ + using namespace Windows::Foundation; +} + +#ifdef _DEBUG +#define breakOnDebug __debugbreak() +#else +#define breakOnDebug +#endif +#define LOG_HR(hr) \ + CLog::LogF(LOGERROR, "function call at line {} ends with error: {}", __LINE__, \ + DX::GetErrorDescription(hr)); +#define CHECK_ERR() if (FAILED(hr)) { LOG_HR(hr); breakOnDebug; return; } +#define RETURN_ERR(ret) if (FAILED(hr)) { LOG_HR(hr); breakOnDebug; return (##ret); } + +bool DX::DeviceResources::CBackBuffer::Acquire(ID3D11Texture2D* pTexture) +{ + if (!pTexture) + return false; + + D3D11_TEXTURE2D_DESC desc; + pTexture->GetDesc(&desc); + + m_width = desc.Width; + m_height = desc.Height; + m_format = desc.Format; + m_usage = desc.Usage; + + m_texture = pTexture; + return true; +} + +std::shared_ptr<DX::DeviceResources> DX::DeviceResources::Get() +{ + static std::shared_ptr<DeviceResources> sDeviceResources(new DeviceResources); + return sDeviceResources; +} + +// Constructor for DeviceResources. +DX::DeviceResources::DeviceResources() + : m_screenViewport() + , m_d3dFeatureLevel(D3D_FEATURE_LEVEL_9_1) + , m_outputSize() + , m_logicalSize() + , m_dpi(DisplayMetrics::Dpi100) + , m_effectiveDpi(DisplayMetrics::Dpi100) + , m_deviceNotify(nullptr) + , m_stereoEnabled(false) + , m_bDeviceCreated(false) + , m_IsHDROutput(false) + , m_IsTransferPQ(false) +{ +} + +DX::DeviceResources::~DeviceResources() = default; + +void DX::DeviceResources::Release() +{ + if (!m_bDeviceCreated) + return; + + ReleaseBackBuffer(); + OnDeviceLost(true); + DestroySwapChain(); + + m_adapter = nullptr; + m_dxgiFactory = nullptr; + m_output = nullptr; + m_deferrContext = nullptr; + m_d3dContext = nullptr; + m_d3dDevice = nullptr; + m_bDeviceCreated = false; +#ifdef _DEBUG + if (m_d3dDebug) + { + m_d3dDebug->ReportLiveDeviceObjects(D3D11_RLDO_SUMMARY | D3D11_RLDO_DETAIL); + m_d3dDebug = nullptr; + } +#endif +} + +void DX::DeviceResources::GetOutput(IDXGIOutput** ppOutput) const +{ + ComPtr<IDXGIOutput> pOutput; + if (!m_swapChain || FAILED(m_swapChain->GetContainingOutput(pOutput.GetAddressOf())) || !pOutput) + m_output.As(&pOutput); + *ppOutput = pOutput.Detach(); +} + +void DX::DeviceResources::GetAdapterDesc(DXGI_ADAPTER_DESC* desc) const +{ + if (m_adapter) + m_adapter->GetDesc(desc); + + // GetDesc() returns VendorId == 0 in Xbox however, we need to know that + // GPU is AMD to apply workarounds in DXVA.cpp CheckCompatibility() same as desktop + if (CSysInfo::GetWindowsDeviceFamily() == CSysInfo::Xbox) + desc->VendorId = PCIV_AMD; +} + +void DX::DeviceResources::GetDisplayMode(DXGI_MODE_DESC* mode) const +{ + DXGI_OUTPUT_DESC outDesc; + ComPtr<IDXGIOutput> pOutput; + DXGI_SWAP_CHAIN_DESC scDesc; + + if (!m_swapChain) + return; + + m_swapChain->GetDesc(&scDesc); + + GetOutput(pOutput.GetAddressOf()); + pOutput->GetDesc(&outDesc); + + // desktop coords depend on DPI + mode->Width = DX::ConvertDipsToPixels(outDesc.DesktopCoordinates.right - outDesc.DesktopCoordinates.left, m_dpi); + mode->Height = DX::ConvertDipsToPixels(outDesc.DesktopCoordinates.bottom - outDesc.DesktopCoordinates.top, m_dpi); + mode->Format = scDesc.BufferDesc.Format; + mode->Scaling = scDesc.BufferDesc.Scaling; + mode->ScanlineOrdering = scDesc.BufferDesc.ScanlineOrdering; + +#ifdef TARGET_WINDOWS_DESKTOP + DEVMODEW sDevMode = {}; + sDevMode.dmSize = sizeof(sDevMode); + + // EnumDisplaySettingsW is only one way to detect current refresh rate + if (EnumDisplaySettingsW(outDesc.DeviceName, ENUM_CURRENT_SETTINGS, &sDevMode)) + { + int i = (((sDevMode.dmDisplayFrequency + 1) % 24) == 0 || ((sDevMode.dmDisplayFrequency + 1) % 30) == 0) ? 1 : 0; + mode->RefreshRate.Numerator = (sDevMode.dmDisplayFrequency + i) * 1000; + mode->RefreshRate.Denominator = 1000 + i; + if (sDevMode.dmDisplayFlags & DM_INTERLACED) + { + mode->RefreshRate.Numerator *= 2; + mode->ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UPPER_FIELD_FIRST; // guessing + } + else + mode->ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_PROGRESSIVE; + } +#else + using namespace winrt::Windows::Graphics::Display::Core; + + auto hdmiInfo = HdmiDisplayInformation::GetForCurrentView(); + if (hdmiInfo) // Xbox only + { + auto currentMode = hdmiInfo.GetCurrentDisplayMode(); + AVRational refresh = av_d2q(currentMode.RefreshRate(), 60000); + mode->RefreshRate.Numerator = refresh.num; + mode->RefreshRate.Denominator = refresh.den; + } +#endif +} + +void DX::DeviceResources::SetViewPort(D3D11_VIEWPORT& viewPort) const +{ + // convert logical viewport to real + D3D11_VIEWPORT realViewPort = + { + viewPort.TopLeftX, + viewPort.TopLeftY, + viewPort.Width, + viewPort.Height, + viewPort.MinDepth, + viewPort.MinDepth + }; + + m_deferrContext->RSSetViewports(1, &realViewPort); +} + +bool DX::DeviceResources::SetFullScreen(bool fullscreen, RESOLUTION_INFO& res) +{ + if (!m_bDeviceCreated || !m_swapChain) + return false; + + critical_section::scoped_lock lock(m_criticalSection); + + BOOL bFullScreen; + m_swapChain->GetFullscreenState(&bFullScreen, nullptr); + + CLog::LogF(LOGDEBUG, "switching from {}({:.0f} x {:.0f}) to {}({} x {})", + bFullScreen ? "fullscreen " : "", m_outputSize.Width, m_outputSize.Height, + fullscreen ? "fullscreen " : "", res.iWidth, res.iHeight); + + bool recreate = m_stereoEnabled != (CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode() == RENDER_STEREO_MODE_HARDWAREBASED); + if (!!bFullScreen && !fullscreen) + { + CLog::LogF(LOGDEBUG, "switching to windowed"); + recreate |= SUCCEEDED(m_swapChain->SetFullscreenState(false, nullptr)); + } + else if (fullscreen) + { + const bool isResValid = res.iWidth > 0 && res.iHeight > 0 && res.fRefreshRate > 0.f; + if (isResValid) + { + DXGI_MODE_DESC currentMode = {}; + GetDisplayMode(¤tMode); + DXGI_SWAP_CHAIN_DESC scDesc; + m_swapChain->GetDesc(&scDesc); + + bool is_interlaced = scDesc.BufferDesc.ScanlineOrdering > DXGI_MODE_SCANLINE_ORDER_PROGRESSIVE; + float refreshRate = res.fRefreshRate; + if (res.dwFlags & D3DPRESENTFLAG_INTERLACED) + refreshRate *= 2; + + if (currentMode.Width != res.iWidth + || currentMode.Height != res.iHeight + || DX::RationalToFloat(currentMode.RefreshRate) != refreshRate + || is_interlaced != (res.dwFlags & D3DPRESENTFLAG_INTERLACED ? true : false) + // force resolution change for stereo mode + // some drivers unable to create stereo swapchain if mode does not match @23.976 + || CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode() == RENDER_STEREO_MODE_HARDWAREBASED) + { + CLog::Log(LOGDEBUG, __FUNCTION__ ": changing display mode to {}x{}@{:0.3f}", res.iWidth, + res.iHeight, res.fRefreshRate, + res.dwFlags & D3DPRESENTFLAG_INTERLACED ? "i" : ""); + + int refresh = static_cast<int>(res.fRefreshRate); + int i = (refresh + 1) % 24 == 0 || (refresh + 1) % 30 == 0 ? 1 : 0; + + currentMode.Width = res.iWidth; + currentMode.Height = res.iHeight; + currentMode.RefreshRate.Numerator = (refresh + i) * 1000; + currentMode.RefreshRate.Denominator = 1000 + i; + if (res.dwFlags & D3DPRESENTFLAG_INTERLACED) + { + currentMode.RefreshRate.Numerator *= 2; + currentMode.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UPPER_FIELD_FIRST; // guessing; + } + // sometimes the OS silently brings Kodi out of full screen mode + // in this case switching a resolution has no any effect and + // we have to enter into full screen mode before switching + if (!bFullScreen) + { + ComPtr<IDXGIOutput> pOutput; + GetOutput(pOutput.GetAddressOf()); + + CLog::LogF(LOGDEBUG, "fixup fullscreen mode before switching resolution"); + recreate |= SUCCEEDED(m_swapChain->SetFullscreenState(true, pOutput.Get())); + m_swapChain->GetFullscreenState(&bFullScreen, nullptr); + } + bool resized = SUCCEEDED(m_swapChain->ResizeTarget(¤tMode)); + if (resized) + { + // some system doesn't inform windowing about desktop size changes + // so we have to change output size before resizing buffers + m_outputSize.Width = static_cast<float>(currentMode.Width); + m_outputSize.Height = static_cast<float>(currentMode.Height); + } + recreate |= resized; + } + } + if (!bFullScreen) + { + ComPtr<IDXGIOutput> pOutput; + GetOutput(pOutput.GetAddressOf()); + + CLog::LogF(LOGDEBUG, "switching to fullscreen"); + recreate |= SUCCEEDED(m_swapChain->SetFullscreenState(true, pOutput.Get())); + } + } + + // resize backbuffer to proper handle fullscreen/stereo transition + if (recreate) + ResizeBuffers(); + + CLog::LogF(LOGDEBUG, "switching done."); + + return recreate; +} + +// Configures resources that don't depend on the Direct3D device. +void DX::DeviceResources::CreateDeviceIndependentResources() +{ +} + +// Configures the Direct3D device, and stores handles to it and the device context. +void DX::DeviceResources::CreateDeviceResources() +{ + CLog::LogF(LOGDEBUG, "creating DirectX 11 device."); + + CreateFactory(); + + UINT creationFlags = D3D11_CREATE_DEVICE_VIDEO_SUPPORT; +#if defined(_DEBUG) + if (DX::SdkLayersAvailable()) + { + // If the project is in a debug build, enable debugging via SDK Layers with this flag. + creationFlags |= D3D11_CREATE_DEVICE_DEBUG; + } +#endif + + // This array defines the set of DirectX hardware feature levels this app will support. + // Note the ordering should be preserved. + // Don't forget to declare your application's minimum required feature level in its + // description. All applications are assumed to support 9.1 unless otherwise stated. + std::vector<D3D_FEATURE_LEVEL> featureLevels; + if (CSysInfo::IsWindowsVersionAtLeast(CSysInfo::WindowsVersionWin10)) + { + featureLevels.push_back(D3D_FEATURE_LEVEL_12_1); + featureLevels.push_back(D3D_FEATURE_LEVEL_12_0); + } + if (CSysInfo::IsWindowsVersionAtLeast(CSysInfo::WindowsVersionWin8)) + featureLevels.push_back(D3D_FEATURE_LEVEL_11_1); + featureLevels.push_back(D3D_FEATURE_LEVEL_11_0); + featureLevels.push_back(D3D_FEATURE_LEVEL_10_1); + featureLevels.push_back(D3D_FEATURE_LEVEL_10_0); + featureLevels.push_back(D3D_FEATURE_LEVEL_9_3); + featureLevels.push_back(D3D_FEATURE_LEVEL_9_2); + featureLevels.push_back(D3D_FEATURE_LEVEL_9_1); + + // Create the Direct3D 11 API device object and a corresponding context. + ComPtr<ID3D11Device> device; + ComPtr<ID3D11DeviceContext> context; + + D3D_DRIVER_TYPE drivertType = m_adapter != nullptr ? D3D_DRIVER_TYPE_UNKNOWN : D3D_DRIVER_TYPE_HARDWARE; + HRESULT hr = D3D11CreateDevice( + m_adapter.Get(), // Create a device on specified adapter. + drivertType, // Create a device using scepcified driver. + nullptr, // Should be 0 unless the driver is D3D_DRIVER_TYPE_SOFTWARE. + creationFlags, // Set debug and Direct2D compatibility flags. + featureLevels.data(), // List of feature levels this app can support. + featureLevels.size(), // Size of the list above. + D3D11_SDK_VERSION, // Always set this to D3D11_SDK_VERSION for Windows Store apps. + &device, // Returns the Direct3D device created. + &m_d3dFeatureLevel, // Returns feature level of device created. + &context // Returns the device immediate context. + ); + + if (FAILED(hr)) + { + CLog::LogF(LOGERROR, "unable to create hardware device, trying to create WARP devices then."); + hr = D3D11CreateDevice( + nullptr, + D3D_DRIVER_TYPE_WARP, // Create a WARP device instead of a hardware device. + nullptr, + creationFlags, + featureLevels.data(), + featureLevels.size(), + D3D11_SDK_VERSION, + &device, + &m_d3dFeatureLevel, + &context + ); + if (FAILED(hr)) + { + CLog::LogF(LOGFATAL, "unable to create WARP device. Rendering in not possible."); + CHECK_ERR(); + } + } + + // Store pointers to the Direct3D 11.1 API device and immediate context. + hr = device.As(&m_d3dDevice); CHECK_ERR(); + + // Check shared textures support + CheckNV12SharedTexturesSupport(); + +#ifdef _DEBUG + if (SUCCEEDED(m_d3dDevice.As(&m_d3dDebug))) + { + ComPtr<ID3D11InfoQueue> d3dInfoQueue; + if (SUCCEEDED(m_d3dDebug.As(&d3dInfoQueue))) + { + std::vector<D3D11_MESSAGE_ID> hide = + { + D3D11_MESSAGE_ID_GETVIDEOPROCESSORFILTERRANGE_UNSUPPORTED, // avoid GETVIDEOPROCESSORFILTERRANGE_UNSUPPORTED (dx bug) + D3D11_MESSAGE_ID_DEVICE_RSSETSCISSORRECTS_NEGATIVESCISSOR // avoid warning for some labels out of screen + // Add more message IDs here as needed + }; + + D3D11_INFO_QUEUE_FILTER filter = {}; + filter.DenyList.NumIDs = hide.size(); + filter.DenyList.pIDList = hide.data(); + d3dInfoQueue->AddStorageFilterEntries(&filter); + } + } +#endif + + hr = context.As(&m_d3dContext); CHECK_ERR(); + hr = m_d3dDevice->CreateDeferredContext1(0, &m_deferrContext); CHECK_ERR(); + + if (!m_adapter) + { + ComPtr<IDXGIDevice1> dxgiDevice; + ComPtr<IDXGIAdapter> adapter; + hr = m_d3dDevice.As(&dxgiDevice); CHECK_ERR(); + hr = dxgiDevice->GetAdapter(&adapter); CHECK_ERR(); + hr = adapter.As(&m_adapter); CHECK_ERR(); + } + + DXGI_ADAPTER_DESC aDesc; + m_adapter->GetDesc(&aDesc); + + CLog::LogF(LOGINFO, "device is created on adapter '{}' with {}", + KODI::PLATFORM::WINDOWS::FromW(aDesc.Description), + GetFeatureLevelDescription(m_d3dFeatureLevel)); + + CheckDXVA2SharedDecoderSurfaces(); + + m_bDeviceCreated = true; +} + +void DX::DeviceResources::ReleaseBackBuffer() +{ + CLog::LogF(LOGDEBUG, "release buffers."); + + m_backBufferTex.Release(); + m_d3dDepthStencilView = nullptr; + if (m_deferrContext) + { + // Clear the previous window size specific context. + ID3D11RenderTargetView* nullViews[] = { nullptr, nullptr, nullptr, nullptr }; + m_deferrContext->OMSetRenderTargets(4, nullViews, nullptr); + FinishCommandList(false); + + m_deferrContext->Flush(); + m_d3dContext->Flush(); + } +} + +void DX::DeviceResources::CreateBackBuffer() +{ + if (!m_bDeviceCreated || !m_swapChain) + return; + + CLog::LogF(LOGDEBUG, "create buffers."); + + // Get swap chain back buffer. + ComPtr<ID3D11Texture2D> backBuffer; + HRESULT hr = m_swapChain->GetBuffer(0, IID_PPV_ARGS(&backBuffer)); CHECK_ERR(); + + // Create back buffer texture from swap chain texture + if (!m_backBufferTex.Acquire(backBuffer.Get())) + { + CLog::LogF(LOGERROR, "failed to create render target."); + return; + } + + // Create a depth stencil view for use with 3D rendering if needed. + CD3D11_TEXTURE2D_DESC depthStencilDesc( + DXGI_FORMAT_D24_UNORM_S8_UINT, + lround(m_outputSize.Width), + lround(m_outputSize.Height), + 1, // This depth stencil view has only one texture. + 1, // Use a single mipmap level. + D3D11_BIND_DEPTH_STENCIL + ); + + ComPtr<ID3D11Texture2D> depthStencil; + hr = m_d3dDevice->CreateTexture2D( + &depthStencilDesc, + nullptr, + &depthStencil + ); CHECK_ERR(); + + CD3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc(D3D11_DSV_DIMENSION_TEXTURE2D); + hr = m_d3dDevice->CreateDepthStencilView( + depthStencil.Get(), + &depthStencilViewDesc, + &m_d3dDepthStencilView + ); CHECK_ERR(); + + // Set the 3D rendering viewport to target the entire window. + m_screenViewport = CD3D11_VIEWPORT( + 0.0f, + 0.0f, + m_outputSize.Width, + m_outputSize.Height + ); + + m_deferrContext->RSSetViewports(1, &m_screenViewport); +} + +HRESULT DX::DeviceResources::CreateSwapChain(DXGI_SWAP_CHAIN_DESC1& desc, DXGI_SWAP_CHAIN_FULLSCREEN_DESC& fsDesc, IDXGISwapChain1** ppSwapChain) const +{ + HRESULT hr; +#ifdef TARGET_WINDOWS_DESKTOP + hr = m_dxgiFactory->CreateSwapChainForHwnd( + m_d3dDevice.Get(), + m_window, + &desc, + &fsDesc, + nullptr, + ppSwapChain + ); RETURN_ERR(hr); + hr = m_dxgiFactory->MakeWindowAssociation(m_window, /*DXGI_MWA_NO_WINDOW_CHANGES |*/ DXGI_MWA_NO_ALT_ENTER); +#else + hr = m_dxgiFactory->CreateSwapChainForCoreWindow( + m_d3dDevice.Get(), + winrt::get_unknown(m_coreWindow), + &desc, + nullptr, + ppSwapChain + ); RETURN_ERR(hr); +#endif + return hr; +} + +void DX::DeviceResources::DestroySwapChain() +{ + if (!m_swapChain) + return; + + BOOL bFullcreen = 0; + m_swapChain->GetFullscreenState(&bFullcreen, nullptr); + if (!!bFullcreen) + m_swapChain->SetFullscreenState(false, nullptr); // mandatory before releasing swapchain + m_swapChain = nullptr; + m_deferrContext->Flush(); + m_d3dContext->Flush(); + m_IsTransferPQ = false; +} + +void DX::DeviceResources::ResizeBuffers() +{ + if (!m_bDeviceCreated) + return; + + CLog::LogF(LOGDEBUG, "resize buffers."); + + bool bHWStereoEnabled = RENDER_STEREO_MODE_HARDWAREBASED == + CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode(); + bool windowed = true; + HRESULT hr = E_FAIL; + DXGI_SWAP_CHAIN_DESC1 scDesc = {}; + + if (m_swapChain) + { + BOOL bFullcreen = 0; + m_swapChain->GetFullscreenState(&bFullcreen, nullptr); + if (!!bFullcreen) + windowed = false; + + m_swapChain->GetDesc1(&scDesc); + if ((scDesc.Stereo == TRUE) != bHWStereoEnabled) // check if swapchain needs to be recreated + DestroySwapChain(); + } + + if (m_swapChain) // If the swap chain already exists, resize it. + { + m_swapChain->GetDesc1(&scDesc); + hr = m_swapChain->ResizeBuffers(scDesc.BufferCount, lround(m_outputSize.Width), + lround(m_outputSize.Height), scDesc.Format, + windowed ? 0 : DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH); + + if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) + { + // If the device was removed for any reason, a new device and swap chain will need to be created. + HandleDeviceLost(hr == DXGI_ERROR_DEVICE_REMOVED); + + // Everything is set up now. Do not continue execution of this method. HandleDeviceLost will reenter this method + // and correctly set up the new device. + return; + } + else if (hr == DXGI_ERROR_INVALID_CALL) + { + // Called when Windows HDR is toggled externally to Kodi. + // Is forced to re-create swap chain to avoid crash. + CreateWindowSizeDependentResources(); + return; + } + CHECK_ERR(); + } + else // Otherwise, create a new one using the same adapter as the existing Direct3D device. + { + HDR_STATUS hdrStatus = CWIN32Util::GetWindowsHDRStatus(); + const bool isHdrEnabled = (hdrStatus == HDR_STATUS::HDR_ON); + bool use10bit = (hdrStatus != HDR_STATUS::HDR_UNSUPPORTED); + +// Xbox needs 10 bit swapchain to output true 4K resolution +#ifdef TARGET_WINDOWS_DESKTOP + DXGI_ADAPTER_DESC ad = {}; + GetAdapterDesc(&ad); + + // Some AMD graphics has issues with 10 bit in SDR. + // Enabled by default only in Intel and NVIDIA with latest drivers/hardware + if (m_d3dFeatureLevel < D3D_FEATURE_LEVEL_12_1 || ad.VendorId == PCIV_AMD) + use10bit = false; +#endif + + // 0 = Auto | 1 = Never | 2 = Always + int use10bitSetting = CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt( + CSettings::SETTING_VIDEOSCREEN_10BITSURFACES); + + if (use10bitSetting == 1) + use10bit = false; + else if (use10bitSetting == 2) + use10bit = true; + + DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {}; + swapChainDesc.Width = lround(m_outputSize.Width); + swapChainDesc.Height = lround(m_outputSize.Height); + swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + swapChainDesc.Stereo = bHWStereoEnabled; + swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; +#ifdef TARGET_WINDOWS_DESKTOP + swapChainDesc.BufferCount = 6; // HDR 60 fps needs 6 buffers to avoid frame drops +#else + swapChainDesc.BufferCount = 3; // Xbox don't like 6 backbuffers (3 is fine even for 4K 60 fps) +#endif + // FLIP_DISCARD improves performance (needed in some systems for 4K HDR 60 fps) + swapChainDesc.SwapEffect = CSysInfo::IsWindowsVersionAtLeast(CSysInfo::WindowsVersionWin10) + ? DXGI_SWAP_EFFECT_FLIP_DISCARD + : DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; + swapChainDesc.Flags = windowed ? 0 : DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH; + swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_IGNORE; + swapChainDesc.SampleDesc.Count = 1; + swapChainDesc.SampleDesc.Quality = 0; + + DXGI_SWAP_CHAIN_FULLSCREEN_DESC scFSDesc = {}; // unused for uwp + scFSDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED; + scFSDesc.Windowed = windowed; + + ComPtr<IDXGISwapChain1> swapChain; + if (m_d3dFeatureLevel >= D3D_FEATURE_LEVEL_11_0 && !bHWStereoEnabled && + (isHdrEnabled || use10bit)) + { + swapChainDesc.Format = DXGI_FORMAT_R10G10B10A2_UNORM; + hr = CreateSwapChain(swapChainDesc, scFSDesc, &swapChain); + if (FAILED(hr)) + { + CLog::LogF(LOGWARNING, "creating 10bit swapchain failed, fallback to 8bit."); + swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; + } + } + + if (!swapChain) + hr = CreateSwapChain(swapChainDesc, scFSDesc, &swapChain); + + if (FAILED(hr) && bHWStereoEnabled) + { + // switch to stereo mode failed, create mono swapchain + CLog::LogF(LOGERROR, "creating stereo swap chain failed with error."); + CLog::LogF(LOGINFO, "fallback to monoscopic mode."); + + swapChainDesc.Stereo = false; + bHWStereoEnabled = false; + + hr = CreateSwapChain(swapChainDesc, scFSDesc, &swapChain); CHECK_ERR(); + + // fallback to split_horizontal mode. + CServiceBroker::GetWinSystem()->GetGfxContext().SetStereoMode( + RENDER_STEREO_MODE_SPLIT_HORIZONTAL); + } + + if (FAILED(hr)) + { + CLog::LogF(LOGERROR, "unable to create swapchain."); + return; + } + + m_IsHDROutput = (swapChainDesc.Format == DXGI_FORMAT_R10G10B10A2_UNORM) && isHdrEnabled; + + const int bits = (swapChainDesc.Format == DXGI_FORMAT_R10G10B10A2_UNORM) ? 10 : 8; + std::string flip = + (swapChainDesc.SwapEffect == DXGI_SWAP_EFFECT_FLIP_DISCARD) ? "discard" : "sequential"; + + CLog::LogF(LOGINFO, "{} bit swapchain is used with {} flip {} buffers and {} output", bits, + swapChainDesc.BufferCount, flip, m_IsHDROutput ? "HDR" : "SDR"); + + hr = swapChain.As(&m_swapChain); CHECK_ERR(); + m_stereoEnabled = bHWStereoEnabled; + + // Ensure that DXGI does not queue more than one frame at a time. This both reduces latency and + // ensures that the application will only render after each VSync, minimizing power consumption. + ComPtr<IDXGIDevice1> dxgiDevice; + hr = m_d3dDevice.As(&dxgiDevice); CHECK_ERR(); + dxgiDevice->SetMaximumFrameLatency(1); + } + + CLog::LogF(LOGDEBUG, "end resize buffers."); +} + +// These resources need to be recreated every time the window size is changed. +void DX::DeviceResources::CreateWindowSizeDependentResources() +{ + ReleaseBackBuffer(); + + DestroySwapChain(); + + if (!m_dxgiFactory->IsCurrent()) // HDR toggling requires re-create factory + CreateFactory(); + + UpdateRenderTargetSize(); + ResizeBuffers(); + + CreateBackBuffer(); +} + +// Determine the dimensions of the render target and whether it will be scaled down. +void DX::DeviceResources::UpdateRenderTargetSize() +{ + m_effectiveDpi = m_dpi; + + // To improve battery life on high resolution devices, render to a smaller render target + // and allow the GPU to scale the output when it is presented. + if (!DisplayMetrics::SupportHighResolutions && m_dpi > DisplayMetrics::DpiThreshold) + { + float width = DX::ConvertDipsToPixels(m_logicalSize.Width, m_dpi); + float height = DX::ConvertDipsToPixels(m_logicalSize.Height, m_dpi); + + // When the device is in portrait orientation, height > width. Compare the + // larger dimension against the width threshold and the smaller dimension + // against the height threshold. + if (std::max(width, height) > DisplayMetrics::WidthThreshold && std::min(width, height) > DisplayMetrics::HeightThreshold) + { + // To scale the app we change the effective DPI. Logical size does not change. + m_effectiveDpi /= 2.0f; + } + } + + // Calculate the necessary render target size in pixels. + m_outputSize.Width = DX::ConvertDipsToPixels(m_logicalSize.Width, m_effectiveDpi); + m_outputSize.Height = DX::ConvertDipsToPixels(m_logicalSize.Height, m_effectiveDpi); + + // Prevent zero size DirectX content from being created. + m_outputSize.Width = std::max(m_outputSize.Width, 1.f); + m_outputSize.Height = std::max(m_outputSize.Height, 1.f); +} + +void DX::DeviceResources::Register(ID3DResource* resource) +{ + critical_section::scoped_lock lock(m_resourceSection); + m_resources.push_back(resource); +} + +void DX::DeviceResources::Unregister(ID3DResource* resource) +{ + critical_section::scoped_lock lock(m_resourceSection); + std::vector<ID3DResource*>::iterator i = find(m_resources.begin(), m_resources.end(), resource); + if (i != m_resources.end()) + m_resources.erase(i); +} + +void DX::DeviceResources::FinishCommandList(bool bExecute) const +{ + if (m_d3dContext == m_deferrContext) + return; + + ComPtr<ID3D11CommandList> pCommandList; + if (FAILED(m_deferrContext->FinishCommandList(true, &pCommandList))) + { + CLog::LogF(LOGERROR, "failed to finish command queue."); + return; + } + + if (bExecute) + m_d3dContext->ExecuteCommandList(pCommandList.Get(), false); +} + +// This method is called in the event handler for the SizeChanged event. +void DX::DeviceResources::SetLogicalSize(float width, float height) +{ + if +#if defined(TARGET_WINDOWS_DESKTOP) + (!m_window) +#else + (!m_coreWindow) +#endif + return; + + CLog::LogF(LOGDEBUG, "receive changing logical size to {:f} x {:f}", width, height); + + if (m_logicalSize.Width != width || m_logicalSize.Height != height) + { + CLog::LogF(LOGDEBUG, "change logical size to {:f} x {:f}", width, height); + + m_logicalSize = winrt::Size(width, height); + + UpdateRenderTargetSize(); + ResizeBuffers(); + } +} + +// This method is called in the event handler for the DpiChanged event. +void DX::DeviceResources::SetDpi(float dpi) +{ + dpi = std::max(dpi, DisplayMetrics::Dpi100); + if (dpi != m_dpi) + m_dpi = dpi; +} + +// This method is called in the event handler for the DisplayContentsInvalidated event. +void DX::DeviceResources::ValidateDevice() +{ + // The D3D Device is no longer valid if the default adapter changed since the device + // was created or if the device has been removed. + + // First, get the information for the default adapter from when the device was created. + ComPtr<IDXGIDevice1> dxgiDevice; + m_d3dDevice.As(&dxgiDevice); + + ComPtr<IDXGIAdapter> deviceAdapter; + dxgiDevice->GetAdapter(&deviceAdapter); + + ComPtr<IDXGIFactory2> dxgiFactory; + deviceAdapter->GetParent(IID_PPV_ARGS(&dxgiFactory)); + + DXGI_ADAPTER_DESC1 previousDesc; + { + ComPtr<IDXGIAdapter1> previousDefaultAdapter; + dxgiFactory->EnumAdapters1(0, &previousDefaultAdapter); + + previousDefaultAdapter->GetDesc1(&previousDesc); + } + + // Next, get the information for the current default adapter. + DXGI_ADAPTER_DESC1 currentDesc; + { + ComPtr<IDXGIFactory1> currentFactory; + CreateDXGIFactory1(IID_PPV_ARGS(¤tFactory)); + + ComPtr<IDXGIAdapter1> currentDefaultAdapter; + currentFactory->EnumAdapters1(0, ¤tDefaultAdapter); + + currentDefaultAdapter->GetDesc1(¤tDesc); + } + // If the adapter LUIDs don't match, or if the device reports that it has been removed, + // a new D3D device must be created. + HRESULT hr = m_d3dDevice->GetDeviceRemovedReason(); + if ( previousDesc.AdapterLuid.LowPart != currentDesc.AdapterLuid.LowPart + || previousDesc.AdapterLuid.HighPart != currentDesc.AdapterLuid.HighPart + || FAILED(hr)) + { + // Release references to resources related to the old device. + dxgiDevice = nullptr; + deviceAdapter = nullptr; + dxgiFactory = nullptr; + + // Create a new device and swap chain. + HandleDeviceLost(hr == DXGI_ERROR_DEVICE_REMOVED); + } +} + +void DX::DeviceResources::OnDeviceLost(bool removed) +{ + auto pGUI = CServiceBroker::GetGUI(); + if (pGUI) + pGUI->GetWindowManager().SendMessage(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_RENDERER_LOST); + + // tell any shared resources + for (auto res : m_resources) + { + // the most of resources like textures and buffers try to + // receive and save their status from current device. + // `removed` means that we have no possibility + // to use the device anymore, tell all resources about this. + res->OnDestroyDevice(removed); + } +} + +void DX::DeviceResources::OnDeviceRestored() +{ + // tell any shared resources + for (auto res : m_resources) + res->OnCreateDevice(); + + auto pGUI = CServiceBroker::GetGUI(); + if (pGUI) + pGUI->GetWindowManager().SendMessage(GUI_MSG_NOTIFY_ALL, 0, 0, GUI_MSG_RENDERER_RESET); +} + +// Recreate all device resources and set them back to the current state. +void DX::DeviceResources::HandleDeviceLost(bool removed) +{ + bool backbuferExists = m_backBufferTex.Get() != nullptr; + + OnDeviceLost(removed); + if (m_deviceNotify != nullptr) + m_deviceNotify->OnDXDeviceLost(); + + if (backbuferExists) + ReleaseBackBuffer(); + + DestroySwapChain(); + + CreateDeviceResources(); + UpdateRenderTargetSize(); + ResizeBuffers(); + + if (backbuferExists) + CreateBackBuffer(); + + if (m_deviceNotify != nullptr) + m_deviceNotify->OnDXDeviceRestored(); + OnDeviceRestored(); + + if (removed) + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_EXECUTE_BUILT_IN, -1, -1, nullptr, + "ReloadSkin"); +} + +bool DX::DeviceResources::Begin() +{ + HRESULT hr = m_swapChain->Present(0, DXGI_PRESENT_TEST); + + // If the device was removed either by a disconnection or a driver upgrade, we + // must recreate all device resources. + if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) + { + HandleDeviceLost(hr == DXGI_ERROR_DEVICE_REMOVED); + } + else + { + // not fatal errors + if (hr == DXGI_ERROR_INVALID_CALL) + { + CreateWindowSizeDependentResources(); + } + } + + m_deferrContext->OMSetRenderTargets(1, m_backBufferTex.GetAddressOfRTV(), m_d3dDepthStencilView.Get()); + + return true; +} + +// Present the contents of the swap chain to the screen. +void DX::DeviceResources::Present() +{ + FinishCommandList(); + + // The first argument instructs DXGI to block until VSync, putting the application + // to sleep until the next VSync. This ensures we don't waste any cycles rendering + // frames that will never be displayed to the screen. + DXGI_PRESENT_PARAMETERS parameters = {}; + HRESULT hr = m_swapChain->Present1(1, 0, ¶meters); + + // If the device was removed either by a disconnection or a driver upgrade, we + // must recreate all device resources. + if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) + { + HandleDeviceLost(hr == DXGI_ERROR_DEVICE_REMOVED); + } + else + { + // not fatal errors + if (hr == DXGI_ERROR_INVALID_CALL) + { + CreateWindowSizeDependentResources(); + } + } + + if (m_d3dContext == m_deferrContext) + { + m_deferrContext->OMSetRenderTargets(1, m_backBufferTex.GetAddressOfRTV(), m_d3dDepthStencilView.Get()); + } +} + +void DX::DeviceResources::ClearDepthStencil() const +{ + m_deferrContext->ClearDepthStencilView(m_d3dDepthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0, 0); +} + +void DX::DeviceResources::ClearRenderTarget(ID3D11RenderTargetView* pRTView, float color[4]) const +{ + m_deferrContext->ClearRenderTargetView(pRTView, color); +} + +void DX::DeviceResources::HandleOutputChange(const std::function<bool(DXGI_OUTPUT_DESC)>& cmpFunc) +{ + DXGI_ADAPTER_DESC currentDesc = {}; + DXGI_ADAPTER_DESC foundDesc = {}; + + ComPtr<IDXGIFactory1> factory; + if (m_adapter) + m_adapter->GetDesc(¤tDesc); + + CreateDXGIFactory1(IID_IDXGIFactory1, &factory); + + ComPtr<IDXGIAdapter1> adapter; + for (int i = 0; factory->EnumAdapters1(i, adapter.ReleaseAndGetAddressOf()) != DXGI_ERROR_NOT_FOUND; i++) + { + adapter->GetDesc(&foundDesc); + ComPtr<IDXGIOutput> output; + for (int j = 0; adapter->EnumOutputs(j, output.ReleaseAndGetAddressOf()) != DXGI_ERROR_NOT_FOUND; j++) + { + DXGI_OUTPUT_DESC outputDesc; + output->GetDesc(&outputDesc); + if (cmpFunc(outputDesc)) + { + output.As(&m_output); + // check if adapter is changed + if (currentDesc.AdapterLuid.HighPart != foundDesc.AdapterLuid.HighPart + || currentDesc.AdapterLuid.LowPart != foundDesc.AdapterLuid.LowPart) + { + // adapter is changed + m_adapter = adapter; + CLog::LogF(LOGDEBUG, "selected {} adapter. ", + KODI::PLATFORM::WINDOWS::FromW(foundDesc.Description)); + // (re)init hooks into new driver + Windowing()->InitHooks(output.Get()); + // recreate d3d11 device on new adapter + if (m_d3dDevice) + HandleDeviceLost(false); + } + return; + } + } + } +} + +bool DX::DeviceResources::CreateFactory() +{ + HRESULT hr; +#if defined(_DEBUG) && defined(TARGET_WINDOWS_STORE) + bool debugDXGI = false; + { + ComPtr<IDXGIInfoQueue> dxgiInfoQueue; + if (SUCCEEDED(DXGIGetDebugInterface1(0, IID_PPV_ARGS(dxgiInfoQueue.GetAddressOf())))) + { + debugDXGI = true; + + hr = CreateDXGIFactory2(DXGI_CREATE_FACTORY_DEBUG, IID_PPV_ARGS(m_dxgiFactory.ReleaseAndGetAddressOf())); RETURN_ERR(false); + + dxgiInfoQueue->SetBreakOnSeverity(DXGI_DEBUG_ALL, DXGI_INFO_QUEUE_MESSAGE_SEVERITY_ERROR, true); + dxgiInfoQueue->SetBreakOnSeverity(DXGI_DEBUG_ALL, DXGI_INFO_QUEUE_MESSAGE_SEVERITY_CORRUPTION, true); + } + } + + if (!debugDXGI) +#endif + hr = CreateDXGIFactory1(IID_PPV_ARGS(m_dxgiFactory.ReleaseAndGetAddressOf())); RETURN_ERR(false); + + return true; +} + +void DX::DeviceResources::SetMonitor(HMONITOR monitor) +{ + HandleOutputChange([monitor](DXGI_OUTPUT_DESC outputDesc) { + return outputDesc.Monitor == monitor; + }); +} + +void DX::DeviceResources::RegisterDeviceNotify(IDeviceNotify* deviceNotify) +{ + m_deviceNotify = deviceNotify; +} + +HMONITOR DX::DeviceResources::GetMonitor() const +{ + if (m_swapChain) + { + ComPtr<IDXGIOutput> output; + GetOutput(output.GetAddressOf()); + if (output) + { + DXGI_OUTPUT_DESC desc; + output->GetDesc(&desc); + return desc.Monitor; + } + } + return nullptr; +} + +bool DX::DeviceResources::IsStereoAvailable() const +{ + if (m_dxgiFactory) + return m_dxgiFactory->IsWindowedStereoEnabled(); + + return false; +} + +void DX::DeviceResources::CheckNV12SharedTexturesSupport() +{ + if (m_d3dFeatureLevel < D3D_FEATURE_LEVEL_10_0 || + CSysInfo::GetWindowsDeviceFamily() != CSysInfo::Desktop) + return; + + D3D11_FEATURE_DATA_D3D11_OPTIONS4 op4 = {}; + HRESULT hr = m_d3dDevice->CheckFeatureSupport(D3D11_FEATURE_D3D11_OPTIONS4, &op4, sizeof(op4)); + m_NV12SharedTexturesSupport = SUCCEEDED(hr) && !!op4.ExtendedNV12SharedTextureSupported; + CLog::LogF(LOGINFO, "extended NV12 shared textures is{}supported", + m_NV12SharedTexturesSupport ? " " : " NOT "); +} + +void DX::DeviceResources::CheckDXVA2SharedDecoderSurfaces() +{ + if (CSysInfo::GetWindowsDeviceFamily() != CSysInfo::Desktop) + return; + + VideoDriverInfo driver = GetVideoDriverVersion(); + + if (!m_NV12SharedTexturesSupport) + return; + + DXGI_ADAPTER_DESC ad = {}; + GetAdapterDesc(&ad); + + m_DXVA2SharedDecoderSurfaces = + ad.VendorId == PCIV_Intel || + (ad.VendorId == PCIV_NVIDIA && driver.valid && driver.majorVersion >= 465) || + (ad.VendorId == PCIV_AMD && driver.valid && driver.majorVersion >= 30 && + m_d3dFeatureLevel >= D3D_FEATURE_LEVEL_12_1); + + CLog::LogF(LOGINFO, "DXVA2 shared decoder surfaces is{}supported", + m_DXVA2SharedDecoderSurfaces ? " " : " NOT "); +} + +VideoDriverInfo DX::DeviceResources::GetVideoDriverVersion() +{ + DXGI_ADAPTER_DESC ad = {}; + GetAdapterDesc(&ad); + + VideoDriverInfo driver = CWIN32Util::GetVideoDriverInfo(ad.VendorId, ad.Description); + + if (ad.VendorId == PCIV_NVIDIA) + CLog::LogF(LOGINFO, "video driver version is {} {}.{} ({})", GetGFXProviderName(ad.VendorId), + driver.majorVersion, driver.minorVersion, driver.version); + else + CLog::LogF(LOGINFO, "video driver version is {} {}", GetGFXProviderName(ad.VendorId), + driver.version); + + return driver; +} + +#if defined(TARGET_WINDOWS_DESKTOP) +// This method is called when the window (WND) is created (or re-created). +void DX::DeviceResources::SetWindow(HWND window) +{ + m_window = window; + + CreateDeviceIndependentResources(); + CreateDeviceResources(); +} +#elif defined(TARGET_WINDOWS_STORE) +// This method is called when the CoreWindow is created (or re-created). +void DX::DeviceResources::SetWindow(const winrt::Windows::UI::Core::CoreWindow& window) +{ + using namespace winrt::Windows::UI::Core; + using namespace winrt::Windows::Graphics::Display; + + m_coreWindow = window; + auto dispatcher = m_coreWindow.Dispatcher(); + DispatchedHandler handler([&]() + { + auto coreWindow = CoreWindow::GetForCurrentThread(); + m_logicalSize = winrt::Size(coreWindow.Bounds().Width, coreWindow.Bounds().Height); + m_dpi = DisplayInformation::GetForCurrentView().LogicalDpi(); + SetWindowPos(coreWindow.Bounds()); + }); + if (dispatcher.HasThreadAccess()) + handler(); + else + dispatcher.RunAsync(CoreDispatcherPriority::High, handler).get(); + + CreateDeviceIndependentResources(); + CreateDeviceResources(); + // we have to call this because we will not get initial WM_SIZE + CreateWindowSizeDependentResources(); +} + +void DX::DeviceResources::SetWindowPos(winrt::Rect rect) +{ + int centerX = rect.X + rect.Width / 2; + int centerY = rect.Y + rect.Height / 2; + + HandleOutputChange([centerX, centerY](DXGI_OUTPUT_DESC outputDesc) { + // DesktopCoordinates depends on the DPI of the desktop + return outputDesc.DesktopCoordinates.left <= centerX && outputDesc.DesktopCoordinates.right >= centerX + && outputDesc.DesktopCoordinates.top <= centerY && outputDesc.DesktopCoordinates.bottom >= centerY; + }); +} + +// Call this method when the app suspends. It provides a hint to the driver that the app +// is entering an idle state and that temporary buffers can be reclaimed for use by other apps. +void DX::DeviceResources::Trim() const +{ + ComPtr<IDXGIDevice3> dxgiDevice; + m_d3dDevice.As(&dxgiDevice); + + dxgiDevice->Trim(); +} + +#endif + +void DX::DeviceResources::SetHdrMetaData(DXGI_HDR_METADATA_HDR10& hdr10) const +{ + ComPtr<IDXGISwapChain4> swapChain4; + + if (!m_swapChain) + return; + + if (SUCCEEDED(m_swapChain.As(&swapChain4))) + { + if (SUCCEEDED(swapChain4->SetHDRMetaData(DXGI_HDR_METADATA_TYPE_HDR10, sizeof(hdr10), &hdr10))) + { + CLog::LogF(LOGDEBUG, + "(raw) RP {} {} | GP {} {} | BP {} {} | WP {} {} | Max ML {} | min ML " + "{} | Max CLL {} | Max FALL {}", + hdr10.RedPrimary[0], hdr10.RedPrimary[1], hdr10.GreenPrimary[0], + hdr10.GreenPrimary[1], hdr10.BluePrimary[0], hdr10.BluePrimary[1], + hdr10.WhitePoint[0], hdr10.WhitePoint[1], hdr10.MaxMasteringLuminance, + hdr10.MinMasteringLuminance, hdr10.MaxContentLightLevel, + hdr10.MaxFrameAverageLightLevel); + + constexpr double FACTOR_1 = 50000.0; + constexpr double FACTOR_2 = 10000.0; + const double RP_0 = static_cast<double>(hdr10.RedPrimary[0]) / FACTOR_1; + const double RP_1 = static_cast<double>(hdr10.RedPrimary[1]) / FACTOR_1; + const double GP_0 = static_cast<double>(hdr10.GreenPrimary[0]) / FACTOR_1; + const double GP_1 = static_cast<double>(hdr10.GreenPrimary[1]) / FACTOR_1; + const double BP_0 = static_cast<double>(hdr10.BluePrimary[0]) / FACTOR_1; + const double BP_1 = static_cast<double>(hdr10.BluePrimary[1]) / FACTOR_1; + const double WP_0 = static_cast<double>(hdr10.WhitePoint[0]) / FACTOR_1; + const double WP_1 = static_cast<double>(hdr10.WhitePoint[1]) / FACTOR_1; + const double Max_ML = static_cast<double>(hdr10.MaxMasteringLuminance) / FACTOR_2; + const double min_ML = static_cast<double>(hdr10.MinMasteringLuminance) / FACTOR_2; + + CLog::LogF(LOGINFO, + "RP {:.3f} {:.3f} | GP {:.3f} {:.3f} | BP {:.3f} {:.3f} | WP {:.3f} " + "{:.3f} | Max ML {:.0f} | min ML {:.4f} | Max CLL {} | Max FALL {}", + RP_0, RP_1, GP_0, GP_1, BP_0, BP_1, WP_0, WP_1, Max_ML, min_ML, + hdr10.MaxContentLightLevel, hdr10.MaxFrameAverageLightLevel); + } + else + { + CLog::LogF(LOGERROR, "DXGI SetHDRMetaData failed"); + } + } +} + +void DX::DeviceResources::SetHdrColorSpace(const DXGI_COLOR_SPACE_TYPE colorSpace) +{ + ComPtr<IDXGISwapChain3> swapChain3; + + if (!m_swapChain) + return; + + if (SUCCEEDED(m_swapChain.As(&swapChain3))) + { + if (SUCCEEDED(swapChain3->SetColorSpace1(colorSpace))) + { + m_IsTransferPQ = (colorSpace == DXGI_COLOR_SPACE_RGB_FULL_G2084_NONE_P2020); + + CLog::LogF(LOGDEBUG, "DXGI SetColorSpace1 success"); + } + else + { + CLog::LogF(LOGERROR, "DXGI SetColorSpace1 failed"); + } + } +} + +HDR_STATUS DX::DeviceResources::ToggleHDR() +{ + DXGI_MODE_DESC md = {}; + GetDisplayMode(&md); + + DX::Windowing()->SetTogglingHDR(true); + DX::Windowing()->SetAlteringWindow(true); + + // Toggle display HDR + HDR_STATUS hdrStatus = CWIN32Util::ToggleWindowsHDR(md); + + // Kill swapchain + if (m_swapChain && hdrStatus != HDR_STATUS::HDR_TOGGLE_FAILED) + { + CLog::LogF(LOGDEBUG, "Re-create swapchain due HDR <-> SDR switch"); + DestroySwapChain(); + } + + DX::Windowing()->SetAlteringWindow(false); + + // Re-create swapchain + if (hdrStatus != HDR_STATUS::HDR_TOGGLE_FAILED) + { + CreateWindowSizeDependentResources(); + + DX::Windowing()->NotifyAppFocusChange(true); + } + + return hdrStatus; +} + +void DX::DeviceResources::ApplyDisplaySettings() +{ + CLog::LogF(LOGDEBUG, "Re-create swapchain due Display Settings changed"); + + DestroySwapChain(); + CreateWindowSizeDependentResources(); +} + +DEBUG_INFO_RENDER DX::DeviceResources::GetDebugInfo() const +{ + if (!m_swapChain) + return {}; + + DXGI_SWAP_CHAIN_DESC1 desc = {}; + m_swapChain->GetDesc1(&desc); + + DXGI_MODE_DESC md = {}; + GetDisplayMode(&md); + + const int bits = (desc.Format == DXGI_FORMAT_R10G10B10A2_UNORM) ? 10 : 8; + const int max = (desc.Format == DXGI_FORMAT_R10G10B10A2_UNORM) ? 1024 : 256; + const int range_min = DX::Windowing()->UseLimitedColor() ? (max * 16) / 256 : 0; + const int range_max = DX::Windowing()->UseLimitedColor() ? (max * 235) / 256 : max - 1; + + DEBUG_INFO_RENDER info; + + info.renderFlags = StringUtils::Format( + "Swapchain: {} buffers, flip {}, {}, EOTF: {} (Windows HDR {})", desc.BufferCount, + (desc.SwapEffect == DXGI_SWAP_EFFECT_FLIP_DISCARD) ? "discard" : "sequential", + Windowing()->IsFullScreen() + ? ((desc.Flags == DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH) ? "fullscreen exclusive" + : "fullscreen windowed") + : "windowed screen", + m_IsTransferPQ ? "PQ" : "SDR", m_IsHDROutput ? "on" : "off"); + + info.videoOutput = StringUtils::Format( + "Surfaces: {}x{}{} @ {:.3f} Hz, pixel: {} {}-bit, range: {} ({}-{})", desc.Width, desc.Height, + (md.ScanlineOrdering > DXGI_MODE_SCANLINE_ORDER_PROGRESSIVE) ? "i" : "p", + static_cast<double>(md.RefreshRate.Numerator) / + static_cast<double>(md.RefreshRate.Denominator), + (desc.Format == DXGI_FORMAT_R10G10B10A2_UNORM) ? "R10G10B10A2" : "B8G8R8A8", bits, + DX::Windowing()->UseLimitedColor() ? "limited" : "full", range_min, range_max); + + return info; +} diff --git a/xbmc/rendering/dx/DeviceResources.h b/xbmc/rendering/dx/DeviceResources.h new file mode 100644 index 0000000..d458902 --- /dev/null +++ b/xbmc/rendering/dx/DeviceResources.h @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2005-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 "DirectXHelper.h" +#include "HDRStatus.h" +#include "guilib/D3DResource.h" + +#include <functional> +#include <memory> + +#include <concrt.h> +#include <dxgi1_5.h> +#include <wrl.h> +#include <wrl/client.h> + +struct RESOLUTION_INFO; +struct DEBUG_INFO_RENDER; +struct VideoDriverInfo; + +namespace DX +{ + interface IDeviceNotify + { + virtual void OnDXDeviceLost() = 0; + virtual void OnDXDeviceRestored() = 0; + }; + + // Controls all the DirectX device resources. + class DeviceResources + { + public: + static std::shared_ptr<DX::DeviceResources> Get(); + + DeviceResources(); + virtual ~DeviceResources(); + void Release(); + + void ValidateDevice(); + void HandleDeviceLost(bool removed); + bool Begin(); + void Present(); + + // The size of the render target, in pixels. + winrt::Windows::Foundation::Size GetOutputSize() const { return m_outputSize; } + // The size of the render target, in dips. + winrt::Windows::Foundation::Size GetLogicalSize() const { return m_logicalSize; } + void SetLogicalSize(float width, float height); + float GetDpi() const { return m_effectiveDpi; } + void SetDpi(float dpi); + + // D3D Accessors. + bool HasValidDevice() const { return m_bDeviceCreated; } + ID3D11Device1* GetD3DDevice() const { return m_d3dDevice.Get(); } + ID3D11DeviceContext1* GetD3DContext() const { return m_deferrContext.Get(); } + ID3D11DeviceContext1* GetImmediateContext() const { return m_d3dContext.Get(); } + IDXGISwapChain1* GetSwapChain() const { return m_swapChain.Get(); } + IDXGIFactory2* GetIDXGIFactory2() const { return m_dxgiFactory.Get(); } + IDXGIAdapter1* GetAdapter() const { return m_adapter.Get(); } + ID3D11DepthStencilView* GetDSV() const { return m_d3dDepthStencilView.Get(); } + D3D_FEATURE_LEVEL GetDeviceFeatureLevel() const { return m_d3dFeatureLevel; } + CD3DTexture& GetBackBuffer() { return m_backBufferTex; } + + void GetOutput(IDXGIOutput** ppOutput) const; + void GetAdapterDesc(DXGI_ADAPTER_DESC *desc) const; + void GetDisplayMode(DXGI_MODE_DESC *mode) const; + + D3D11_VIEWPORT GetScreenViewport() const { return m_screenViewport; } + void SetViewPort(D3D11_VIEWPORT& viewPort) const; + + void ReleaseBackBuffer(); + void CreateBackBuffer(); + void ResizeBuffers(); + + bool SetFullScreen(bool fullscreen, RESOLUTION_INFO& res); + + // Apply display settings changes + void ApplyDisplaySettings(); + + // HDR display support + HDR_STATUS ToggleHDR(); + void SetHdrMetaData(DXGI_HDR_METADATA_HDR10& hdr10) const; + void SetHdrColorSpace(const DXGI_COLOR_SPACE_TYPE colorSpace); + bool IsHDROutput() const { return m_IsHDROutput; } + bool IsTransferPQ() const { return m_IsTransferPQ; } + + // DX resources registration + void Register(ID3DResource *resource); + void Unregister(ID3DResource *resource); + + void FinishCommandList(bool bExecute = true) const; + void ClearDepthStencil() const; + void ClearRenderTarget(ID3D11RenderTargetView* pRTView, float color[4]) const; + void RegisterDeviceNotify(IDeviceNotify* deviceNotify); + + bool IsStereoAvailable() const; + bool IsStereoEnabled() const { return m_stereoEnabled; } + void SetStereoIdx(byte idx) { m_backBufferTex.SetViewIdx(idx); } + + void SetMonitor(HMONITOR monitor); + HMONITOR GetMonitor() const; +#if defined(TARGET_WINDOWS_DESKTOP) + void SetWindow(HWND window); +#elif defined(TARGET_WINDOWS_STORE) + void Trim() const; + void SetWindow(const winrt::Windows::UI::Core::CoreWindow& window); + void SetWindowPos(winrt::Windows::Foundation::Rect rect); +#endif // TARGET_WINDOWS_STORE + bool IsNV12SharedTexturesSupported() const { return m_NV12SharedTexturesSupport; } + bool IsDXVA2SharedDecoderSurfaces() const { return m_DXVA2SharedDecoderSurfaces; } + + // Gets debug info from swapchain + DEBUG_INFO_RENDER GetDebugInfo() const; + + private: + class CBackBuffer : public CD3DTexture + { + public: + CBackBuffer() : CD3DTexture() {} + void SetViewIdx(unsigned idx) { m_viewIdx = idx; } + bool Acquire(ID3D11Texture2D* pTexture); + }; + + HRESULT CreateSwapChain(DXGI_SWAP_CHAIN_DESC1 &desc, DXGI_SWAP_CHAIN_FULLSCREEN_DESC &fsDesc, IDXGISwapChain1 **ppSwapChain) const; + void DestroySwapChain(); + void CreateDeviceIndependentResources(); + void CreateDeviceResources(); + void CreateWindowSizeDependentResources(); + void UpdateRenderTargetSize(); + void OnDeviceLost(bool removed); + void OnDeviceRestored(); + void HandleOutputChange(const std::function<bool(DXGI_OUTPUT_DESC)>& cmpFunc); + bool CreateFactory(); + void CheckNV12SharedTexturesSupport(); + VideoDriverInfo GetVideoDriverVersion(); + void CheckDXVA2SharedDecoderSurfaces(); + + HWND m_window{ nullptr }; +#if defined(TARGET_WINDOWS_STORE) + winrt::Windows::UI::Core::CoreWindow m_coreWindow = nullptr; +#endif + Microsoft::WRL::ComPtr<IDXGIFactory2> m_dxgiFactory; + Microsoft::WRL::ComPtr<IDXGIAdapter1> m_adapter; + Microsoft::WRL::ComPtr<IDXGIOutput1> m_output; + + Microsoft::WRL::ComPtr<ID3D11Device1> m_d3dDevice; + Microsoft::WRL::ComPtr<ID3D11DeviceContext1> m_d3dContext; + Microsoft::WRL::ComPtr<ID3D11DeviceContext1> m_deferrContext; + Microsoft::WRL::ComPtr<IDXGISwapChain1> m_swapChain; +#ifdef _DEBUG + Microsoft::WRL::ComPtr<ID3D11Debug> m_d3dDebug; +#endif + + CBackBuffer m_backBufferTex; + Microsoft::WRL::ComPtr<ID3D11DepthStencilView> m_d3dDepthStencilView; + D3D11_VIEWPORT m_screenViewport; + + // Cached device properties. + D3D_FEATURE_LEVEL m_d3dFeatureLevel; + winrt::Windows::Foundation::Size m_outputSize; + winrt::Windows::Foundation::Size m_logicalSize; + float m_dpi; + + // This is the DPI that will be reported back to the app. It takes into account whether the app supports high resolution screens or not. + float m_effectiveDpi; + // The IDeviceNotify can be held directly as it owns the DeviceResources. + IDeviceNotify* m_deviceNotify; + + // scritical section + Concurrency::critical_section m_criticalSection; + Concurrency::critical_section m_resourceSection; + std::vector<ID3DResource*> m_resources; + + bool m_stereoEnabled; + bool m_bDeviceCreated; + bool m_IsHDROutput; + bool m_IsTransferPQ; + bool m_NV12SharedTexturesSupport{false}; + bool m_DXVA2SharedDecoderSurfaces{false}; + }; +} diff --git a/xbmc/rendering/dx/DirectXHelper.h b/xbmc/rendering/dx/DirectXHelper.h new file mode 100644 index 0000000..bb51ca6 --- /dev/null +++ b/xbmc/rendering/dx/DirectXHelper.h @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2005-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 "commons/Exception.h" +#include "dxerr.h" + +#include "platform/win32/CharsetConverter.h" + +#include <d3d11_4.h> +#include <ppltasks.h> // For create_task + +enum PCI_Vendors +{ + PCIV_AMD = 0x1002, + PCIV_NVIDIA = 0x10DE, + PCIV_Intel = 0x8086, +}; + +namespace DX +{ +#define RATIONAL_TO_FLOAT(rational) ((rational.Denominator != 0) ? \ + static_cast<float>(rational.Numerator) / static_cast<float>(rational.Denominator) : 0.0f) + + namespace DisplayMetrics + { + // High resolution displays can require a lot of GPU and battery power to render. + // High resolution phones, for example, may suffer from poor battery life if + // games attempt to render at 60 frames per second at full fidelity. + // The decision to render at full fidelity across all platforms and form factors + // should be deliberate. + static const bool SupportHighResolutions = true; + + // The default thresholds that define a "high resolution" display. If the thresholds + // are exceeded and SupportHighResolutions is false, the dimensions will be scaled + // by 50%. + static const float Dpi100 = 96.0f; // 100% of standard desktop display. + static const float DpiThreshold = 192.0f; // 200% of standard desktop display. + static const float WidthThreshold = 1920.0f; // 1080p width. + static const float HeightThreshold = 1080.0f; // 1080p height. + }; + + inline void BreakIfFailed(HRESULT hr) + { + if (FAILED(hr)) + { + // Set a breakpoint on this line to catch Win32 API errors. +#if _DEBUG && !defined(TARGET_WINDOWS_STORE) + DebugBreak(); +#endif + throw new XbmcCommons::UncheckedException(__FUNCTION__, "Unhandled error"); + } + } + + // Converts a length in device-independent pixels (DIPs) to a length in physical pixels. + inline float ConvertDipsToPixels(float dips, float dpi) + { + static const float dipsPerInch = DisplayMetrics::Dpi100; + return floorf(dips * dpi / dipsPerInch + 0.5f); // Round to nearest integer. + } + + inline float ConvertPixelsToDips(float pixels, float dpi) + { + static const float dipsPerInch = DisplayMetrics::Dpi100; + return floorf(pixels / (dpi / dipsPerInch) + 0.5f); // Round to nearest integer. + } + + inline float RationalToFloat(DXGI_RATIONAL rational) + { + return RATIONAL_TO_FLOAT(rational); + } + + inline void GetRefreshRatio(uint32_t refresh, uint32_t *num, uint32_t *den) + { + int i = (((refresh + 1) % 24) == 0 || ((refresh + 1) % 30) == 0) ? 1 : 0; + *num = (refresh + i) * 1000; + *den = 1000 + i; + } + + inline std::string GetErrorDescription(HRESULT hr) + { + using namespace KODI::PLATFORM::WINDOWS; + + WCHAR buff[2048]; + DXGetErrorDescriptionW(hr, buff, 2048); + + return FromW(StringUtils::Format(L"{:X} - {} ({})", hr, DXGetErrorStringW(hr), buff)); + } + + inline std::string GetFeatureLevelDescription(D3D_FEATURE_LEVEL featureLevel) + { + uint32_t fl_major = (featureLevel & 0xF000u) >> 12; + uint32_t fl_minor = (featureLevel & 0x0F00u) >> 8; + + return StringUtils::Format("D3D_FEATURE_LEVEL_{}_{}", fl_major, fl_minor); + } + + inline std::string GetGFXProviderName(UINT vendorId) + { + std::string name; + switch (vendorId) + { + case PCIV_AMD: + name = "AMD"; + break; + case PCIV_Intel: + name = "Intel"; + break; + case PCIV_NVIDIA: + name = "NVIDIA"; + break; + } + + return name; + } + + template <typename T> struct SizeGen + { + SizeGen<T>() { Width = Height = 0; } + SizeGen<T>(T width, T height) { Width = width; Height = height; } + + bool operator !=(const SizeGen<T> &size) const + { + return Width != size.Width || Height != size.Height; + } + + const SizeGen<T> &operator -=(const SizeGen<T> &size) + { + Width -= size.Width; + Height -= size.Height; + return *this; + }; + + const SizeGen<T> &operator +=(const SizeGen<T> &size) + { + Width += size.Width; + Height += size.Height; + return *this; + }; + + const SizeGen<T> &operator -=(const T &size) + { + Width -= size; + Height -= size; + return *this; + }; + + const SizeGen<T> &operator +=(const T &size) + { + Width += size; + Height += size; + return *this; + }; + + T Width, Height; + }; + +#if defined(_DEBUG) + // Check for SDK Layer support. + inline bool SdkLayersAvailable() + { + HRESULT hr = D3D11CreateDevice( + nullptr, + D3D_DRIVER_TYPE_NULL, // There is no need to create a real hardware device. + nullptr, + D3D11_CREATE_DEVICE_DEBUG, // Check for the SDK layers. + nullptr, // Any feature level will do. + 0, + D3D11_SDK_VERSION, // Always set this to D3D11_SDK_VERSION for Windows Store apps. + nullptr, // No need to keep the D3D device reference. + nullptr, // No need to know the feature level. + nullptr // No need to keep the D3D device context reference. + ); + + return SUCCEEDED(hr); + } +#endif +} + +#ifdef TARGET_WINDOWS_DESKTOP +namespace winrt +{ + namespace Windows + { + namespace Foundation + { + typedef DX::SizeGen<float> Size; + typedef DX::SizeGen<int> SizeInt; + } + } +} +#endif diff --git a/xbmc/rendering/dx/RenderContext.h b/xbmc/rendering/dx/RenderContext.h new file mode 100644 index 0000000..9878691 --- /dev/null +++ b/xbmc/rendering/dx/RenderContext.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2005-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 + +#if defined(TARGET_WINDOWS_DESKTOP) +#include "windowing/windows/WinSystemWin32DX.h" +#elif defined(TARGET_WINDOWS_STORE) +#include "windowing/win10/WinSystemWin10DX.h" +#endif +#include "ServiceBroker.h" + +namespace DX +{ +#if defined(TARGET_WINDOWS_DESKTOP) + __inline CWinSystemWin32DX* Windowing() + { + return dynamic_cast<CWinSystemWin32DX*>(CServiceBroker::GetRenderSystem()); + } +#elif defined(TARGET_WINDOWS_STORE) + __inline CWinSystemWin10DX* Windowing() + { + return dynamic_cast<CWinSystemWin10DX*>(CServiceBroker::GetRenderSystem()); + } +#endif +} diff --git a/xbmc/rendering/dx/RenderSystemDX.cpp b/xbmc/rendering/dx/RenderSystemDX.cpp new file mode 100644 index 0000000..cb1b0d5 --- /dev/null +++ b/xbmc/rendering/dx/RenderSystemDX.cpp @@ -0,0 +1,706 @@ +/* + * Copyright (C) 2005-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 "RenderSystemDX.h" + +#include "application/Application.h" + +#include <mutex> +#if defined(TARGET_WINDOWS_DESKTOP) +#include "cores/RetroPlayer/process/windows/RPProcessInfoWin.h" +#include "cores/RetroPlayer/rendering/VideoRenderers/RPWinRenderer.h" +#endif +#include "cores/VideoPlayer/DVDCodecs/DVDFactoryCodec.h" +#include "cores/VideoPlayer/DVDCodecs/Video/DXVA.h" +#if defined(TARGET_WINDOWS_STORE) +#include "cores/VideoPlayer/Process/windows/ProcessInfoWin10.h" +#else +#include "cores/VideoPlayer/Process/windows/ProcessInfoWin.h" +#endif +#include "cores/VideoPlayer/VideoRenderers/RenderFactory.h" +#include "cores/VideoPlayer/VideoRenderers/WinRenderer.h" +#include "cores/VideoPlayer/VideoRenderers/RenderManager.h" +#include "guilib/D3DResource.h" +#include "guilib/GUIShaderDX.h" +#include "guilib/GUITextureD3D.h" +#include "guilib/GUIWindowManager.h" +#include "utils/MathUtils.h" +#include "utils/log.h" + +#include <DirectXPackedVector.h> + +extern "C" { +#include <libavutil/pixfmt.h> +} + +using namespace KODI; +using namespace DirectX; +using namespace DirectX::PackedVector; +using namespace Microsoft::WRL; +using namespace std::chrono_literals; + +CRenderSystemDX::CRenderSystemDX() : CRenderSystemBase() + , m_interlaced(false) +{ + m_bVSync = true; + + memset(&m_viewPort, 0, sizeof m_viewPort); + memset(&m_scissor, 0, sizeof m_scissor); +} + +CRenderSystemDX::~CRenderSystemDX() = default; + +bool CRenderSystemDX::InitRenderSystem() +{ + m_bVSync = true; + + // check various device capabilities + CheckDeviceCaps(); + + if (!CreateStates() || !InitGUIShader()) + return false; + + m_bRenderCreated = true; + m_deviceResources->RegisterDeviceNotify(this); + + // register platform dependent objects +#if defined(TARGET_WINDOWS_STORE) + VIDEOPLAYER::CProcessInfoWin10::Register(); +#else + VIDEOPLAYER::CProcessInfoWin::Register(); +#endif + CDVDFactoryCodec::ClearHWAccels(); + DXVA::CDecoder::Register(); + VIDEOPLAYER::CRendererFactory::ClearRenderer(); + CWinRenderer::Register(); +#if defined(TARGET_WINDOWS_DESKTOP) + RETRO::CRPProcessInfoWin::Register(); + RETRO::CRPProcessInfoWin::RegisterRendererFactory(new RETRO::CWinRendererFactory); +#endif + m_viewPort = m_deviceResources->GetScreenViewport(); + RestoreViewPort(); + + auto outputSize = m_deviceResources->GetOutputSize(); + // set camera to center of screen + CPoint camPoint = { outputSize.Width * 0.5f, outputSize.Height * 0.5f }; + SetCameraPosition(camPoint, outputSize.Width, outputSize.Height); + + DXGI_ADAPTER_DESC AIdentifier = {}; + m_deviceResources->GetAdapterDesc(&AIdentifier); + m_RenderRenderer = KODI::PLATFORM::WINDOWS::FromW(AIdentifier.Description); + uint32_t version = 0; + for (uint32_t decimal = m_deviceResources->GetDeviceFeatureLevel() >> 8, round = 0; decimal > 0; decimal >>= 4, ++round) + version += (decimal % 16) * std::pow(10, round); + m_RenderVersion = StringUtils::Format("{:.1f}", static_cast<float>(version) / 10.0f); + + CGUITextureD3D::Register(); + + return true; +} + +void CRenderSystemDX::OnResize() +{ + if (!m_bRenderCreated) + return; + + auto outputSize = m_deviceResources->GetOutputSize(); + + // set camera to center of screen + CPoint camPoint = { outputSize.Width * 0.5f, outputSize.Height * 0.5f }; + SetCameraPosition(camPoint, outputSize.Width, outputSize.Height); + + CheckInterlacedStereoView(); +} + +bool CRenderSystemDX::IsFormatSupport(DXGI_FORMAT format, unsigned int usage) const +{ + ComPtr<ID3D11Device1> pD3DDev = m_deviceResources->GetD3DDevice(); + UINT supported; + pD3DDev->CheckFormatSupport(format, &supported); + return (supported & usage) != 0; +} + +bool CRenderSystemDX::DestroyRenderSystem() +{ + std::unique_lock<CCriticalSection> lock(m_resourceSection); + + if (m_pGUIShader) + { + m_pGUIShader->End(); + delete m_pGUIShader; + m_pGUIShader = nullptr; + } + m_rightEyeTex.Release(); + m_BlendEnableState = nullptr; + m_BlendDisableState = nullptr; + m_RSScissorDisable = nullptr; + m_RSScissorEnable = nullptr; + m_depthStencilState = nullptr; + + return true; +} + +void CRenderSystemDX::CheckInterlacedStereoView() +{ + RENDER_STEREO_MODE stereoMode = CServiceBroker::GetWinSystem()->GetGfxContext().GetStereoMode(); + + if ( m_rightEyeTex.Get() + && RENDER_STEREO_MODE_INTERLACED != stereoMode + && RENDER_STEREO_MODE_CHECKERBOARD != stereoMode) + { + m_rightEyeTex.Release(); + } + + if ( !m_rightEyeTex.Get() + && ( RENDER_STEREO_MODE_INTERLACED == stereoMode + || RENDER_STEREO_MODE_CHECKERBOARD == stereoMode)) + { + const auto outputSize = m_deviceResources->GetOutputSize(); + DXGI_FORMAT texFormat = m_deviceResources->GetBackBuffer().GetFormat(); + if (!m_rightEyeTex.Create(outputSize.Width, outputSize.Height, 1, D3D11_USAGE_DEFAULT, texFormat)) + { + CLog::Log(LOGERROR, "{} - Failed to create right eye buffer.", __FUNCTION__); + CServiceBroker::GetWinSystem()->GetGfxContext().SetStereoMode(RENDER_STEREO_MODE_SPLIT_HORIZONTAL); // try fallback to split horizontal + } + else + m_deviceResources->Unregister(&m_rightEyeTex); // we will handle its health + } +} + +bool CRenderSystemDX::CreateStates() +{ + auto m_pD3DDev = m_deviceResources->GetD3DDevice(); + auto m_pContext = m_deviceResources->GetD3DContext(); + + if (!m_pD3DDev) + return false; + + m_BlendEnableState = nullptr; + m_BlendDisableState = nullptr; + + // Initialize the description of the stencil state. + D3D11_DEPTH_STENCIL_DESC depthStencilDesc = {}; + + // Set up the description of the stencil state. + depthStencilDesc.DepthEnable = false; + depthStencilDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL; + depthStencilDesc.DepthFunc = D3D11_COMPARISON_NEVER; + depthStencilDesc.StencilEnable = false; + depthStencilDesc.StencilReadMask = 0xFF; + depthStencilDesc.StencilWriteMask = 0xFF; + + // Stencil operations if pixel is front-facing. + depthStencilDesc.FrontFace.StencilFailOp = D3D11_STENCIL_OP_KEEP; + depthStencilDesc.FrontFace.StencilDepthFailOp = D3D11_STENCIL_OP_INCR; + depthStencilDesc.FrontFace.StencilPassOp = D3D11_STENCIL_OP_KEEP; + depthStencilDesc.FrontFace.StencilFunc = D3D11_COMPARISON_ALWAYS; + + // Stencil operations if pixel is back-facing. + depthStencilDesc.BackFace.StencilFailOp = D3D11_STENCIL_OP_KEEP; + depthStencilDesc.BackFace.StencilDepthFailOp = D3D11_STENCIL_OP_DECR; + depthStencilDesc.BackFace.StencilPassOp = D3D11_STENCIL_OP_KEEP; + depthStencilDesc.BackFace.StencilFunc = D3D11_COMPARISON_ALWAYS; + + // Create the depth stencil state. + HRESULT hr = m_pD3DDev->CreateDepthStencilState(&depthStencilDesc, &m_depthStencilState); + if(FAILED(hr)) + return false; + + // Set the depth stencil state. + m_pContext->OMSetDepthStencilState(m_depthStencilState.Get(), 0); + + D3D11_RASTERIZER_DESC rasterizerState; + rasterizerState.CullMode = D3D11_CULL_NONE; + rasterizerState.FillMode = D3D11_FILL_SOLID;// DEBUG - D3D11_FILL_WIREFRAME + rasterizerState.FrontCounterClockwise = false; + rasterizerState.DepthBias = 0; + rasterizerState.DepthBiasClamp = 0.0f; + rasterizerState.DepthClipEnable = true; + rasterizerState.SlopeScaledDepthBias = 0.0f; + rasterizerState.ScissorEnable = false; + rasterizerState.MultisampleEnable = false; + rasterizerState.AntialiasedLineEnable = false; + + if (FAILED(m_pD3DDev->CreateRasterizerState(&rasterizerState, &m_RSScissorDisable))) + return false; + + rasterizerState.ScissorEnable = true; + if (FAILED(m_pD3DDev->CreateRasterizerState(&rasterizerState, &m_RSScissorEnable))) + return false; + + m_pContext->RSSetState(m_RSScissorDisable.Get()); // by default + + D3D11_BLEND_DESC blendState = {}; + blendState.RenderTarget[0].BlendEnable = true; + blendState.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA; // D3D11_BLEND_SRC_ALPHA; + blendState.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA; // D3D11_BLEND_INV_SRC_ALPHA; + blendState.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD; + blendState.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ONE; + blendState.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_INV_SRC_ALPHA; + blendState.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD; + blendState.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL; + + m_pD3DDev->CreateBlendState(&blendState, &m_BlendEnableState); + + blendState.RenderTarget[0].BlendEnable = false; + m_pD3DDev->CreateBlendState(&blendState, &m_BlendDisableState); + + // by default + m_pContext->OMSetBlendState(m_BlendEnableState.Get(), nullptr, 0xFFFFFFFF); + m_BlendEnabled = true; + + return true; +} + +void CRenderSystemDX::PresentRender(bool rendered, bool videoLayer) +{ + if (!m_bRenderCreated) + return; + + if ( rendered + && ( m_stereoMode == RENDER_STEREO_MODE_INTERLACED + || m_stereoMode == RENDER_STEREO_MODE_CHECKERBOARD)) + { + auto m_pContext = m_deviceResources->GetD3DContext(); + + // all views prepared, let's merge them before present + m_pContext->OMSetRenderTargets(1, m_deviceResources->GetBackBuffer().GetAddressOfRTV(), m_deviceResources->GetDSV()); + + auto outputSize = m_deviceResources->GetOutputSize(); + CRect destRect = { 0.0f, 0.0f, float(outputSize.Width), float(outputSize.Height) }; + + SHADER_METHOD method = RENDER_STEREO_MODE_INTERLACED == m_stereoMode + ? SHADER_METHOD_RENDER_STEREO_INTERLACED_RIGHT + : SHADER_METHOD_RENDER_STEREO_CHECKERBOARD_RIGHT; + SetAlphaBlendEnable(true); + CD3DTexture::DrawQuad(destRect, 0, &m_rightEyeTex, nullptr, method); + CD3DHelper::PSClearShaderResources(m_pContext); + } + + // time for decoder that may require the context + { + std::unique_lock<CCriticalSection> lock(m_decoderSection); + XbmcThreads::EndTime<> timer; + timer.Set(5ms); + while (!m_decodingTimer.IsTimePast() && !timer.IsTimePast()) + { + m_decodingEvent.wait(lock, 1ms); + } + } + + PresentRenderImpl(rendered); +} + +void CRenderSystemDX::RequestDecodingTime() +{ + std::unique_lock<CCriticalSection> lock(m_decoderSection); + m_decodingTimer.Set(3ms); +} + +void CRenderSystemDX::ReleaseDecodingTime() +{ + std::unique_lock<CCriticalSection> lock(m_decoderSection); + m_decodingTimer.SetExpired(); + m_decodingEvent.notify(); +} + +bool CRenderSystemDX::BeginRender() +{ + if (!m_bRenderCreated) + return false; + + m_limitedColorRange = CServiceBroker::GetWinSystem()->UseLimitedColor(); + m_inScene = m_deviceResources->Begin(); + return m_inScene; +} + +bool CRenderSystemDX::EndRender() +{ + m_inScene = false; + + if (!m_bRenderCreated) + return false; + + return true; +} + +bool CRenderSystemDX::ClearBuffers(UTILS::COLOR::Color color) +{ + if (!m_bRenderCreated) + return false; + + float fColor[4]; + CD3DHelper::XMStoreColor(fColor, color); + ID3D11RenderTargetView* pRTView = m_deviceResources->GetBackBuffer().GetRenderTarget(); + + if ( m_stereoMode != RENDER_STEREO_MODE_OFF + && m_stereoMode != RENDER_STEREO_MODE_MONO) + { + // if stereo anaglyph/tab/sbs, data was cleared when left view was rendered + if (m_stereoView == RENDER_STEREO_VIEW_RIGHT) + { + // execute command's queue + m_deviceResources->FinishCommandList(); + + // do not clear RT for anaglyph modes + if ( m_stereoMode == RENDER_STEREO_MODE_ANAGLYPH_GREEN_MAGENTA + || m_stereoMode == RENDER_STEREO_MODE_ANAGLYPH_RED_CYAN + || m_stereoMode == RENDER_STEREO_MODE_ANAGLYPH_YELLOW_BLUE) + { + pRTView = nullptr; + } + // for interlaced/checkerboard clear view for right texture + else if (m_stereoMode == RENDER_STEREO_MODE_INTERLACED + || m_stereoMode == RENDER_STEREO_MODE_CHECKERBOARD) + { + pRTView = m_rightEyeTex.GetRenderTarget(); + } + } + } + + if (pRTView == nullptr) + return true; + + auto outputSize = m_deviceResources->GetOutputSize(); + CRect clRect(0.0f, 0.0f, + static_cast<float>(outputSize.Width), + static_cast<float>(outputSize.Height)); + + // Unlike Direct3D 9, D3D11 ClearRenderTargetView always clears full extent of the resource view. + // Viewport and scissor settings are not applied. So clear RT by drawing full sized rect with clear color + if (m_ScissorsEnabled && m_scissor != clRect) + { + bool alphaEnabled = m_BlendEnabled; + if (alphaEnabled) + SetAlphaBlendEnable(false); + + CGUITextureD3D::DrawQuad(clRect, color); + + if (alphaEnabled) + SetAlphaBlendEnable(true); + } + else + m_deviceResources->ClearRenderTarget(pRTView, fColor); + + m_deviceResources->ClearDepthStencil(); + return true; +} + +void CRenderSystemDX::CaptureStateBlock() +{ + if (!m_bRenderCreated) + return; +} + +void CRenderSystemDX::ApplyStateBlock() +{ + if (!m_bRenderCreated) + return; + + auto m_pContext = m_deviceResources->GetD3DContext(); + + m_pContext->RSSetState(m_ScissorsEnabled ? m_RSScissorEnable.Get() : m_RSScissorDisable.Get()); + m_pContext->OMSetDepthStencilState(m_depthStencilState.Get(), 0); + float factors[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; + m_pContext->OMSetBlendState(m_BlendEnabled ? m_BlendEnableState.Get() : m_BlendDisableState.Get(), factors, 0xFFFFFFFF); + + m_pGUIShader->ApplyStateBlock(); +} + +void CRenderSystemDX::SetCameraPosition(const CPoint &camera, int screenWidth, int screenHeight, float stereoFactor) +{ + if (!m_bRenderCreated) + return; + + // grab the viewport dimensions and location + float w = m_viewPort.Width * 0.5f; + float h = m_viewPort.Height * 0.5f; + + XMFLOAT2 offset = XMFLOAT2(camera.x - screenWidth*0.5f, camera.y - screenHeight*0.5f); + + // world view. Until this is moved onto the GPU (via a vertex shader for instance), we set it to the identity here. + m_pGUIShader->SetWorld(XMMatrixIdentity()); + + // Initialize the view matrix camera view. + // Multiply the Y coord by -1 then translate so that everything is relative to the camera position. + XMMATRIX flipY = XMMatrixScaling(1.0, -1.0f, 1.0f); + XMMATRIX translate = XMMatrixTranslation(-(w + offset.x - stereoFactor), -(h + offset.y), 2 * h); + m_pGUIShader->SetView(XMMatrixMultiply(translate, flipY)); + + // projection onto screen space + m_pGUIShader->SetProjection(XMMatrixPerspectiveOffCenterLH((-w - offset.x)*0.5f, (w - offset.x)*0.5f, (-h + offset.y)*0.5f, (h + offset.y)*0.5f, h, 100 * h)); +} + +void CRenderSystemDX::Project(float &x, float &y, float &z) +{ + if (!m_bRenderCreated) + return; + + m_pGUIShader->Project(x, y, z); +} + +CRect CRenderSystemDX::GetBackBufferRect() +{ + auto outputSize = m_deviceResources->GetOutputSize(); + return CRect(0.f, 0.f, static_cast<float>(outputSize.Width), static_cast<float>(outputSize.Height)); +} + +void CRenderSystemDX::GetViewPort(CRect& viewPort) +{ + if (!m_bRenderCreated) + return; + + viewPort.x1 = m_viewPort.TopLeftX; + viewPort.y1 = m_viewPort.TopLeftY; + viewPort.x2 = m_viewPort.TopLeftX + m_viewPort.Width; + viewPort.y2 = m_viewPort.TopLeftY + m_viewPort.Height; +} + +void CRenderSystemDX::SetViewPort(const CRect& viewPort) +{ + if (!m_bRenderCreated) + return; + + m_viewPort.MinDepth = 0.0f; + m_viewPort.MaxDepth = 1.0f; + m_viewPort.TopLeftX = viewPort.x1; + m_viewPort.TopLeftY = viewPort.y1; + m_viewPort.Width = viewPort.x2 - viewPort.x1; + m_viewPort.Height = viewPort.y2 - viewPort.y1; + + m_deviceResources->SetViewPort(m_viewPort); + m_pGUIShader->SetViewPort(m_viewPort); +} + +void CRenderSystemDX::RestoreViewPort() +{ + if (!m_bRenderCreated) + return; + + m_deviceResources->SetViewPort(m_viewPort); + m_pGUIShader->SetViewPort(m_viewPort); +} + +bool CRenderSystemDX::ScissorsCanEffectClipping() +{ + if (!m_bRenderCreated) + return false; + + return m_pGUIShader != nullptr && m_pGUIShader->HardwareClipIsPossible(); +} + +CRect CRenderSystemDX::ClipRectToScissorRect(const CRect &rect) +{ + if (!m_bRenderCreated) + return CRect(); + + float xFactor = m_pGUIShader->GetClipXFactor(); + float xOffset = m_pGUIShader->GetClipXOffset(); + float yFactor = m_pGUIShader->GetClipYFactor(); + float yOffset = m_pGUIShader->GetClipYOffset(); + + return CRect(rect.x1 * xFactor + xOffset, + rect.y1 * yFactor + yOffset, + rect.x2 * xFactor + xOffset, + rect.y2 * yFactor + yOffset); +} + +void CRenderSystemDX::SetScissors(const CRect& rect) +{ + if (!m_bRenderCreated) + return; + + auto m_pContext = m_deviceResources->GetD3DContext(); + + m_scissor = rect; + CD3D11_RECT scissor(MathUtils::round_int(rect.x1) + , MathUtils::round_int(rect.y1) + , MathUtils::round_int(rect.x2) + , MathUtils::round_int(rect.y2)); + + m_pContext->RSSetScissorRects(1, &scissor); + m_pContext->RSSetState(m_RSScissorEnable.Get()); + m_ScissorsEnabled = true; +} + +void CRenderSystemDX::ResetScissors() +{ + if (!m_bRenderCreated) + return; + + auto m_pContext = m_deviceResources->GetD3DContext(); + auto outputSize = m_deviceResources->GetOutputSize(); + + m_scissor.SetRect(0.0f, 0.0f, + static_cast<float>(outputSize.Width), + static_cast<float>(outputSize.Height)); + + m_pContext->RSSetState(m_RSScissorDisable.Get()); + m_ScissorsEnabled = false; +} + +void CRenderSystemDX::OnDXDeviceLost() +{ + CRenderSystemDX::DestroyRenderSystem(); +} + +void CRenderSystemDX::OnDXDeviceRestored() +{ + CRenderSystemDX::InitRenderSystem(); +} + +void CRenderSystemDX::SetStereoMode(RENDER_STEREO_MODE mode, RENDER_STEREO_VIEW view) +{ + CRenderSystemBase::SetStereoMode(mode, view); + + if (!m_bRenderCreated) + return; + + auto m_pContext = m_deviceResources->GetD3DContext(); + + UINT writeMask = D3D11_COLOR_WRITE_ENABLE_ALL; + if(m_stereoMode == RENDER_STEREO_MODE_ANAGLYPH_RED_CYAN) + { + if(m_stereoView == RENDER_STEREO_VIEW_LEFT) + writeMask = D3D11_COLOR_WRITE_ENABLE_RED; + else if(m_stereoView == RENDER_STEREO_VIEW_RIGHT) + writeMask = D3D11_COLOR_WRITE_ENABLE_BLUE | D3D11_COLOR_WRITE_ENABLE_GREEN; + } + if(m_stereoMode == RENDER_STEREO_MODE_ANAGLYPH_GREEN_MAGENTA) + { + if(m_stereoView == RENDER_STEREO_VIEW_LEFT) + writeMask = D3D11_COLOR_WRITE_ENABLE_GREEN; + else if(m_stereoView == RENDER_STEREO_VIEW_RIGHT) + writeMask = D3D11_COLOR_WRITE_ENABLE_BLUE | D3D11_COLOR_WRITE_ENABLE_RED; + } + if (m_stereoMode == RENDER_STEREO_MODE_ANAGLYPH_YELLOW_BLUE) + { + if (m_stereoView == RENDER_STEREO_VIEW_LEFT) + writeMask = D3D11_COLOR_WRITE_ENABLE_RED | D3D11_COLOR_WRITE_ENABLE_GREEN; + else if (m_stereoView == RENDER_STEREO_VIEW_RIGHT) + writeMask = D3D11_COLOR_WRITE_ENABLE_BLUE; + } + if ( RENDER_STEREO_MODE_INTERLACED == m_stereoMode + || RENDER_STEREO_MODE_CHECKERBOARD == m_stereoMode) + { + if (m_stereoView == RENDER_STEREO_VIEW_RIGHT) + { + m_pContext->OMSetRenderTargets(1, m_rightEyeTex.GetAddressOfRTV(), m_deviceResources->GetDSV()); + } + } + else if (RENDER_STEREO_MODE_HARDWAREBASED == m_stereoMode) + { + m_deviceResources->SetStereoIdx(m_stereoView == RENDER_STEREO_VIEW_RIGHT ? 1 : 0); + + m_pContext->OMSetRenderTargets(1, m_deviceResources->GetBackBuffer().GetAddressOfRTV(), m_deviceResources->GetDSV()); + } + + auto m_pD3DDev = m_deviceResources->GetD3DDevice(); + + D3D11_BLEND_DESC desc; + m_BlendEnableState->GetDesc(&desc); + // update blend state + if (desc.RenderTarget[0].RenderTargetWriteMask != writeMask) + { + m_BlendDisableState = nullptr; + m_BlendEnableState = nullptr; + + desc.RenderTarget[0].RenderTargetWriteMask = writeMask; + m_pD3DDev->CreateBlendState(&desc, &m_BlendEnableState); + + desc.RenderTarget[0].BlendEnable = false; + m_pD3DDev->CreateBlendState(&desc, &m_BlendDisableState); + + float blendFactors[] = { 0.0f, 0.0f, 0.0f, 0.0f }; + m_pContext->OMSetBlendState(m_BlendEnabled ? m_BlendEnableState.Get() : m_BlendDisableState.Get(), blendFactors, 0xFFFFFFFF); + } +} + +bool CRenderSystemDX::SupportsStereo(RENDER_STEREO_MODE mode) const +{ + switch (mode) + { + case RENDER_STEREO_MODE_ANAGLYPH_RED_CYAN: + case RENDER_STEREO_MODE_ANAGLYPH_GREEN_MAGENTA: + case RENDER_STEREO_MODE_ANAGLYPH_YELLOW_BLUE: + case RENDER_STEREO_MODE_INTERLACED: + case RENDER_STEREO_MODE_CHECKERBOARD: + return true; + case RENDER_STEREO_MODE_HARDWAREBASED: + return m_deviceResources->IsStereoAvailable(); + default: + return CRenderSystemBase::SupportsStereo(mode); + } +} + +void CRenderSystemDX::FlushGPU() const +{ + if (!m_bRenderCreated) + return; + + m_deviceResources->FinishCommandList(); + m_deviceResources->GetImmediateContext()->Flush(); +} + +bool CRenderSystemDX::InitGUIShader() +{ + delete m_pGUIShader; + m_pGUIShader = nullptr; + + m_pGUIShader = new CGUIShaderDX(); + if (!m_pGUIShader->Initialize()) + { + CLog::LogF(LOGERROR, "Failed to initialize GUI shader."); + return false; + } + + m_pGUIShader->ApplyStateBlock(); + return true; +} + +void CRenderSystemDX::SetAlphaBlendEnable(bool enable) +{ + if (!m_bRenderCreated) + return; + + m_deviceResources->GetD3DContext()->OMSetBlendState(enable ? m_BlendEnableState.Get() : m_BlendDisableState.Get(), nullptr, 0xFFFFFFFF); + m_BlendEnabled = enable; +} + +CD3DTexture& CRenderSystemDX::GetBackBuffer() +{ + if (m_stereoView == RENDER_STEREO_VIEW_RIGHT && m_rightEyeTex.Get()) + return m_rightEyeTex; + + return m_deviceResources->GetBackBuffer(); +} + +void CRenderSystemDX::CheckDeviceCaps() +{ + const auto feature_level = m_deviceResources->GetDeviceFeatureLevel(); + if (feature_level < D3D_FEATURE_LEVEL_9_3) + m_maxTextureSize = D3D_FL9_1_REQ_TEXTURE2D_U_OR_V_DIMENSION; + else if (feature_level < D3D_FEATURE_LEVEL_10_0) + m_maxTextureSize = D3D_FL9_3_REQ_TEXTURE2D_U_OR_V_DIMENSION; + else if (feature_level < D3D_FEATURE_LEVEL_11_0) + m_maxTextureSize = D3D10_REQ_TEXTURE2D_U_OR_V_DIMENSION; + else + // 11_x and greater feature level. Limit this size to avoid memory overheads + m_maxTextureSize = D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION >> 1; +} + +bool CRenderSystemDX::SupportsNPOT(bool dxt) const +{ + // MSDN says: + // At feature levels 9_1, 9_2 and 9_3, the display device supports the use + // of 2D textures with dimensions that are not powers of two under two conditions: + // 1) only one MIP-map level for each texture can be created - we are using both 1 and 0 mipmap levels + // 2) no wrap sampler modes for textures are allowed - we are using clamp everywhere + // At feature levels 10_0, 10_1 and 11_0, the display device unconditionally supports the use of 2D textures with dimensions that are not powers of two. + // taking in account first condition we setup caps NPOT for FE > 9.x only + return m_deviceResources->GetDeviceFeatureLevel() > D3D_FEATURE_LEVEL_9_3 ? true : false; +} diff --git a/xbmc/rendering/dx/RenderSystemDX.h b/xbmc/rendering/dx/RenderSystemDX.h new file mode 100644 index 0000000..9a18e22 --- /dev/null +++ b/xbmc/rendering/dx/RenderSystemDX.h @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2005-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 "DeviceResources.h" +#include "rendering/RenderSystem.h" +#include "threads/Condition.h" +#include "threads/CriticalSection.h" +#include "threads/SystemClock.h" +#include "utils/ColorUtils.h" + +#include <wrl/client.h> + +class ID3DResource; +class CGUIShaderDX; +enum AVPixelFormat; +enum AVPixelFormat; + +class CRenderSystemDX : public CRenderSystemBase, DX::IDeviceNotify +{ +public: + CRenderSystemDX(); + virtual ~CRenderSystemDX(); + + // CRenderBase overrides + bool InitRenderSystem() override; + bool DestroyRenderSystem() override; + bool BeginRender() override; + bool EndRender() override; + void PresentRender(bool rendered, bool videoLayer) override; + bool ClearBuffers(UTILS::COLOR::Color color) override; + void SetViewPort(const CRect& viewPort) override; + void GetViewPort(CRect& viewPort) override; + void RestoreViewPort() override; + CRect ClipRectToScissorRect(const CRect &rect) override; + bool ScissorsCanEffectClipping() override; + void SetScissors(const CRect &rect) override; + void ResetScissors() override; + void CaptureStateBlock() override; + void ApplyStateBlock() override; + void SetCameraPosition(const CPoint &camera, int screenWidth, int screenHeight, float stereoFactor = 0.f) override; + void SetStereoMode(RENDER_STEREO_MODE mode, RENDER_STEREO_VIEW view) override; + bool SupportsStereo(RENDER_STEREO_MODE mode) const override; + void Project(float &x, float &y, float &z) override; + bool SupportsNPOT(bool dxt) const override; + + // IDeviceNotify overrides + void OnDXDeviceLost() override; + void OnDXDeviceRestored() override; + + // CRenderSystemDX methods + CGUIShaderDX* GetGUIShader() const { return m_pGUIShader; } + bool Interlaced() const { return m_interlaced; } + bool IsFormatSupport(DXGI_FORMAT format, unsigned int usage) const; + CRect GetBackBufferRect(); + CD3DTexture& GetBackBuffer(); + + void FlushGPU() const; + void RequestDecodingTime(); + void ReleaseDecodingTime(); + void SetAlphaBlendEnable(bool enable); + + // empty overrides + bool IsExtSupported(const char* extension) const override { return false; } + bool ResetRenderSystem(int width, int height) override { return true; } + +protected: + virtual void PresentRenderImpl(bool rendered) = 0; + + bool CreateStates(); + bool InitGUIShader(); + void OnResize(); + void CheckInterlacedStereoView(void); + void CheckDeviceCaps(); + + CCriticalSection m_resourceSection; + CCriticalSection m_decoderSection; + + // our adapter could change as we go + bool m_interlaced; + bool m_inScene{ false }; ///< True if we're in a BeginScene()/EndScene() block + bool m_BlendEnabled{ false }; + bool m_ScissorsEnabled{ false }; + D3D11_VIEWPORT m_viewPort; + CRect m_scissor; + CGUIShaderDX* m_pGUIShader{ nullptr }; + Microsoft::WRL::ComPtr<ID3D11DepthStencilState> m_depthStencilState; + Microsoft::WRL::ComPtr<ID3D11BlendState> m_BlendEnableState; + Microsoft::WRL::ComPtr<ID3D11BlendState> m_BlendDisableState; + Microsoft::WRL::ComPtr<ID3D11RasterizerState> m_RSScissorDisable; + Microsoft::WRL::ComPtr<ID3D11RasterizerState> m_RSScissorEnable; + // stereo interlaced/checkerboard intermediate target + CD3DTexture m_rightEyeTex; + + XbmcThreads::EndTime<> m_decodingTimer; + XbmcThreads::ConditionVariable m_decodingEvent; + + std::shared_ptr<DX::DeviceResources> m_deviceResources; +}; + diff --git a/xbmc/rendering/dx/ScreenshotSurfaceWindows.cpp b/xbmc/rendering/dx/ScreenshotSurfaceWindows.cpp new file mode 100644 index 0000000..7a62ad6 --- /dev/null +++ b/xbmc/rendering/dx/ScreenshotSurfaceWindows.cpp @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2005-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 "ScreenshotSurfaceWindows.h" + +#include "ServiceBroker.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "rendering/dx/DeviceResources.h" +#include "utils/Screenshot.h" +#include "utils/log.h" +#include "windowing/GraphicContext.h" + +#include <mutex> + +#include <wrl/client.h> + +using namespace Microsoft::WRL; + +void CScreenshotSurfaceWindows::Register() +{ + CScreenShot::Register(CScreenshotSurfaceWindows::CreateSurface); +} + +std::unique_ptr<IScreenshotSurface> CScreenshotSurfaceWindows::CreateSurface() +{ + return std::unique_ptr<CScreenshotSurfaceWindows>(new CScreenshotSurfaceWindows()); +} + +bool CScreenshotSurfaceWindows::Capture() +{ + CWinSystemBase* winsystem = CServiceBroker::GetWinSystem(); + if (!winsystem) + return false; + + CGUIComponent* gui = CServiceBroker::GetGUI(); + if (!gui) + return false; + + std::unique_lock<CCriticalSection> lock(winsystem->GetGfxContext()); + gui->GetWindowManager().Render(); + + auto deviceResources = DX::DeviceResources::Get(); + deviceResources->FinishCommandList(); + + ComPtr<ID3D11DeviceContext> pImdContext = deviceResources->GetImmediateContext(); + ComPtr<ID3D11Device> pDevice = deviceResources->GetD3DDevice(); + CD3DTexture& backbuffer = deviceResources->GetBackBuffer(); + if (!backbuffer.Get()) + return false; + + D3D11_TEXTURE2D_DESC desc = {}; + backbuffer.GetDesc(&desc); + desc.Usage = D3D11_USAGE_STAGING; + desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ; + desc.BindFlags = 0; + + ComPtr<ID3D11Texture2D> pCopyTexture = nullptr; + if (SUCCEEDED(pDevice->CreateTexture2D(&desc, nullptr, &pCopyTexture))) + { + // take copy + pImdContext->CopyResource(pCopyTexture.Get(), backbuffer.Get()); + + D3D11_MAPPED_SUBRESOURCE res; + if (SUCCEEDED(pImdContext->Map(pCopyTexture.Get(), 0, D3D11_MAP_READ, 0, &res))) + { + m_width = desc.Width; + m_height = desc.Height; + m_stride = res.RowPitch; + m_buffer = new unsigned char[m_height * m_stride]; + if (desc.Format == DXGI_FORMAT_R10G10B10A2_UNORM) + { + // convert R10G10B10A2 -> B8G8R8A8 + for (int y = 0; y < m_height; y++) + { + uint32_t* pixels10 = reinterpret_cast<uint32_t*>(static_cast<uint8_t*>(res.pData) + y * res.RowPitch); + uint8_t* pixels8 = m_buffer + y * m_stride; + + for (int x = 0; x < m_width; x++, pixels10++, pixels8 += 4) + { + // actual bit per channel is A2B10G10R10 + uint32_t pixel = *pixels10; + // R + pixels8[2] = static_cast<uint8_t>((pixel & 0x3FF) * 255 / 1023); + // G + pixel >>= 10; + pixels8[1] = static_cast<uint8_t>((pixel & 0x3FF) * 255 / 1023); + // B + pixel >>= 10; + pixels8[0] = static_cast<uint8_t>((pixel & 0x3FF) * 255 / 1023); + // A + pixels8[3] = 0xFF; + } + } + } + else + memcpy(m_buffer, res.pData, m_height * m_stride); + pImdContext->Unmap(pCopyTexture.Get(), 0); + } + else + CLog::LogF(LOGERROR, "MAP_READ failed."); + } + + return m_buffer != nullptr; +} diff --git a/xbmc/rendering/dx/ScreenshotSurfaceWindows.h b/xbmc/rendering/dx/ScreenshotSurfaceWindows.h new file mode 100644 index 0000000..e492e44 --- /dev/null +++ b/xbmc/rendering/dx/ScreenshotSurfaceWindows.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2005-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 "utils/IScreenshotSurface.h" + +#include <memory> + +class CScreenshotSurfaceWindows : public IScreenshotSurface +{ +public: + static void Register(); + static std::unique_ptr<IScreenshotSurface> CreateSurface(); + + bool Capture() override; +}; diff --git a/xbmc/rendering/gl/CMakeLists.txt b/xbmc/rendering/gl/CMakeLists.txt new file mode 100644 index 0000000..6aadafa --- /dev/null +++ b/xbmc/rendering/gl/CMakeLists.txt @@ -0,0 +1,12 @@ +if(OPENGL_FOUND) + set(SOURCES RenderSystemGL.cpp + ScreenshotSurfaceGL.cpp + GLShader.cpp) + + set(HEADERS RenderSystemGL.h + ScreenshotSurfaceGL.h + GLShader.h) + + core_add_library(rendering_gl) +endif() + diff --git a/xbmc/rendering/gl/GLShader.cpp b/xbmc/rendering/gl/GLShader.cpp new file mode 100644 index 0000000..e91af97 --- /dev/null +++ b/xbmc/rendering/gl/GLShader.cpp @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2005-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 "GLShader.h" + +#include "ServiceBroker.h" +#include "rendering/MatrixGL.h" +#include "rendering/RenderSystem.h" +#include "utils/log.h" +#include "windowing/GraphicContext.h" + +using namespace Shaders; + +CGLShader::CGLShader(const char* shader, const std::string& prefix) +{ + m_proj = nullptr; + m_model = nullptr; + m_clipPossible = false; + + VertexShader()->LoadSource("gl_shader_vert.glsl"); + PixelShader()->LoadSource(shader, prefix); +} + +CGLShader::CGLShader(const char* vshader, const char* fshader, const std::string& prefix) +{ + m_proj = nullptr; + m_model = nullptr; + m_clipPossible = false; + + VertexShader()->LoadSource(vshader, prefix); + PixelShader()->LoadSource(fshader, prefix); +} + +void CGLShader::OnCompiledAndLinked() +{ + // This is called after CompileAndLink() + + // Variables passed directly to the Fragment shader + m_hTex0 = glGetUniformLocation(ProgramHandle(), "m_samp0"); + m_hTex1 = glGetUniformLocation(ProgramHandle(), "m_samp1"); + m_hUniCol = glGetUniformLocation(ProgramHandle(), "m_unicol"); + + // Variables passed directly to the Vertex shader + m_hProj = glGetUniformLocation(ProgramHandle(), "m_proj"); + m_hModel = glGetUniformLocation(ProgramHandle(), "m_model"); + + // Vertex attributes + m_hPos = glGetAttribLocation(ProgramHandle(), "m_attrpos"); + m_hCol = glGetAttribLocation(ProgramHandle(), "m_attrcol"); + m_hCord0 = glGetAttribLocation(ProgramHandle(), "m_attrcord0"); + m_hCord1 = glGetAttribLocation(ProgramHandle(), "m_attrcord1"); + + // It's okay to do this only one time. Textures units never change. + glUseProgram(ProgramHandle()); + glUniform1i(m_hTex0, 0); + glUniform1i(m_hTex1, 1); + glUniform4f(m_hUniCol, 1.0, 1.0, 1.0, 1.0); + + glUseProgram(0); +} + +bool CGLShader::OnEnabled() +{ + // This is called after glUseProgram() + + const GLfloat *projMatrix = glMatrixProject.Get(); + const GLfloat *modelMatrix = glMatrixModview.Get(); + glUniformMatrix4fv(m_hProj, 1, GL_FALSE, projMatrix); + glUniformMatrix4fv(m_hModel, 1, GL_FALSE, modelMatrix); + + const TransformMatrix &guiMatrix = CServiceBroker::GetWinSystem()->GetGfxContext().GetGUIMatrix(); + CRect viewPort; // absolute positions of corners + CServiceBroker::GetRenderSystem()->GetViewPort(viewPort); + + /* glScissor operates in window coordinates. In order that we can use it to + * perform clipping, we must ensure that there is an independent linear + * transformation from the coordinate system used by CGraphicContext::ClipRect + * to window coordinates, separately for X and Y (in other words, no + * rotation or shear is introduced at any stage). To do, this, we need to + * check that zeros are present in the following locations: + * + * GUI matrix: + * / * 0 * * \ + * | 0 * * * | + * \ 0 0 * * / + * ^ TransformMatrix::TransformX/Y/ZCoord are only ever called with + * input z = 0, so this column doesn't matter + * Model-view matrix: + * / * 0 0 * \ + * | 0 * 0 * | + * | 0 0 * * | + * \ * * * * / <- eye w has no influence on window x/y (last column below + * is either 0 or ignored) + * Projection matrix: + * / * 0 0 0 \ + * | 0 * 0 0 | + * | * * * * | <- normalised device coordinate z has no influence on window x/y + * \ 0 0 * 0 / + * + * Some of these zeros are not strictly required to ensure this, but they tend + * to be zeroed in the common case, so by checking for zeros here, we simplify + * the calculation of the window x/y coordinates further down the line. + * + * (Minor detail: we don't quite deal in window coordinates as defined by + * OpenGL, because CRenderSystemGLES::SetScissors flips the Y axis. But all + * that's needed to handle that is an effective negation at the stage where + * Y is in normalised device coordinates.) + */ + m_clipPossible = guiMatrix.m[0][1] == 0 && + guiMatrix.m[1][0] == 0 && + guiMatrix.m[2][0] == 0 && + guiMatrix.m[2][1] == 0 && + modelMatrix[0+1*4] == 0 && + modelMatrix[0+2*4] == 0 && + modelMatrix[1+0*4] == 0 && + modelMatrix[1+2*4] == 0 && + modelMatrix[2+0*4] == 0 && + modelMatrix[2+1*4] == 0 && + projMatrix[0+1*4] == 0 && + projMatrix[0+2*4] == 0 && + projMatrix[0+3*4] == 0 && + projMatrix[1+0*4] == 0 && + projMatrix[1+2*4] == 0 && + projMatrix[1+3*4] == 0 && + projMatrix[3+0*4] == 0 && + projMatrix[3+1*4] == 0 && + projMatrix[3+3*4] == 0; + + m_clipXFactor = 0.0; + m_clipXOffset = 0.0; + m_clipYFactor = 0.0; + m_clipYOffset = 0.0; + + if (m_clipPossible) + { + m_clipXFactor = guiMatrix.m[0][0] * modelMatrix[0+0*4] * projMatrix[0+0*4]; + m_clipXOffset = (guiMatrix.m[0][3] * modelMatrix[0+0*4] + modelMatrix[0+3*4]) * projMatrix[0+0*4]; + m_clipYFactor = guiMatrix.m[1][1] * modelMatrix[1+1*4] * projMatrix[1+1*4]; + m_clipYOffset = (guiMatrix.m[1][3] * modelMatrix[1+1*4] + modelMatrix[1+3*4]) * projMatrix[1+1*4]; + float clipW = (guiMatrix.m[2][3] * modelMatrix[2+2*4] + modelMatrix[2+3*4]) * projMatrix[3+2*4]; + float xMult = (viewPort.x2 - viewPort.x1) / (2 * clipW); + float yMult = (viewPort.y1 - viewPort.y2) / (2 * clipW); // correct for inverted window coordinate scheme + m_clipXFactor = m_clipXFactor * xMult; + m_clipXOffset = m_clipXOffset * xMult + (viewPort.x2 + viewPort.x1) / 2; + m_clipYFactor = m_clipYFactor * yMult; + m_clipYOffset = m_clipYOffset * yMult + (viewPort.y2 + viewPort.y1) / 2; + } + + return true; +} + +void CGLShader::Free() +{ + // Do Cleanup here + CGLSLShaderProgram::Free(); +} diff --git a/xbmc/rendering/gl/GLShader.h b/xbmc/rendering/gl/GLShader.h new file mode 100644 index 0000000..d5b82f3 --- /dev/null +++ b/xbmc/rendering/gl/GLShader.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2005-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 "guilib/Shader.h" + +#include <string> + +class CGLShader : public Shaders::CGLSLShaderProgram +{ +public: + CGLShader(const char* shader, const std::string& prefix); + CGLShader(const char* vshader, const char* fshader, const std::string& prefix); + void OnCompiledAndLinked() override; + bool OnEnabled() override; + void Free(); + + GLint GetPosLoc() {return m_hPos;} + GLint GetColLoc() {return m_hCol;} + GLint GetCord0Loc() {return m_hCord0;} + GLint GetCord1Loc() {return m_hCord1;} + GLint GetUniColLoc() {return m_hUniCol;} + GLint GetModelLoc() {return m_hModel; } + bool HardwareClipIsPossible() {return m_clipPossible; } + GLfloat GetClipXFactor() {return m_clipXFactor; } + GLfloat GetClipXOffset() {return m_clipXOffset; } + GLfloat GetClipYFactor() {return m_clipYFactor; } + GLfloat GetClipYOffset() {return m_clipYOffset; } + +protected: + GLint m_hTex0 = 0; + GLint m_hTex1 = 0; + GLint m_hUniCol = 0; + GLint m_hProj = 0; + GLint m_hModel = 0; + GLint m_hPos = 0; + GLint m_hCol = 0; + GLint m_hCord0 = 0; + GLint m_hCord1 = 0; + + const GLfloat *m_proj = nullptr; + const GLfloat *m_model = nullptr; + + bool m_clipPossible = false; + GLfloat m_clipXFactor; + GLfloat m_clipXOffset; + GLfloat m_clipYFactor; + GLfloat m_clipYOffset; +}; diff --git a/xbmc/rendering/gl/RenderSystemGL.cpp b/xbmc/rendering/gl/RenderSystemGL.cpp new file mode 100644 index 0000000..46a354c --- /dev/null +++ b/xbmc/rendering/gl/RenderSystemGL.cpp @@ -0,0 +1,795 @@ +/* + * Copyright (C) 2005-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 "RenderSystemGL.h" + +#include "ServiceBroker.h" +#include "URL.h" +#include "guilib/GUITextureGL.h" +#include "rendering/MatrixGL.h" +#include "utils/FileUtils.h" +#include "utils/GLUtils.h" +#include "utils/MathUtils.h" +#include "utils/XTimeUtils.h" +#include "utils/log.h" +#include "windowing/WinSystem.h" + +using namespace std::chrono_literals; + +CRenderSystemGL::CRenderSystemGL() : CRenderSystemBase() +{ +} + +CRenderSystemGL::~CRenderSystemGL() = default; + +bool CRenderSystemGL::InitRenderSystem() +{ + m_bVSync = false; + m_bVsyncInit = false; + m_maxTextureSize = 2048; + + // Get the GL version number + m_RenderVersionMajor = 0; + m_RenderVersionMinor = 0; + const char* ver = (const char*)glGetString(GL_VERSION); + if (ver != 0) + { + sscanf(ver, "%d.%d", &m_RenderVersionMajor, &m_RenderVersionMinor); + m_RenderVersion = ver; + } + + CLog::Log(LOGINFO, "CRenderSystemGL::{} - Version: {}, Major: {}, Minor: {}", __FUNCTION__, ver, + m_RenderVersionMajor, m_RenderVersionMinor); + + m_RenderExtensions = " "; + if (m_RenderVersionMajor > 3 || + (m_RenderVersionMajor == 3 && m_RenderVersionMinor >= 2)) + { + GLint n = 0; + glGetIntegerv(GL_NUM_EXTENSIONS, &n); + if (n > 0) + { + GLint i; + for (i = 0; i < n; i++) + { + m_RenderExtensions += (const char*) glGetStringi(GL_EXTENSIONS, i); + m_RenderExtensions += " "; + } + } + } + else + { + auto extensions = (const char*) glGetString(GL_EXTENSIONS); + if (extensions) + { + m_RenderExtensions += extensions; + } + } + m_RenderExtensions += " "; + + ver = (const char*)glGetString(GL_SHADING_LANGUAGE_VERSION); + if (ver) + { + sscanf(ver, "%d.%d", &m_glslMajor, &m_glslMinor); + } + else + { + m_glslMajor = 1; + m_glslMinor = 0; + } + + LogGraphicsInfo(); + + // Get our driver vendor and renderer + const char* tmpVendor = (const char*) glGetString(GL_VENDOR); + m_RenderVendor.clear(); + if (tmpVendor != NULL) + m_RenderVendor = tmpVendor; + + const char* tmpRenderer = (const char*) glGetString(GL_RENDERER); + m_RenderRenderer.clear(); + if (tmpRenderer != NULL) + m_RenderRenderer = tmpRenderer; + + m_bRenderCreated = true; + + if (m_RenderVersionMajor > 3 || + (m_RenderVersionMajor == 3 && m_RenderVersionMinor >= 2)) + { + glGenVertexArrays(1, &m_vertexArray); + glBindVertexArray(m_vertexArray); + } + + InitialiseShaders(); + + CGUITextureGL::Register(); + + return true; +} + +bool CRenderSystemGL::ResetRenderSystem(int width, int height) +{ + if (!m_bRenderCreated) + return false; + + m_width = width; + m_height = height; + + if (m_RenderVersionMajor > 3 || + (m_RenderVersionMajor == 3 && m_RenderVersionMinor >= 2)) + { + glBindVertexArray(0); + glDeleteVertexArrays(1, &m_vertexArray); + glGenVertexArrays(1, &m_vertexArray); + glBindVertexArray(m_vertexArray); + } + + ReleaseShaders(); + InitialiseShaders(); + + glClearColor( 0.0f, 0.0f, 0.0f, 0.0f ); + + CalculateMaxTexturesize(); + + CRect rect( 0, 0, width, height ); + SetViewPort( rect ); + + glEnable(GL_SCISSOR_TEST); + + glMatrixProject.Clear(); + glMatrixProject->LoadIdentity(); + glMatrixProject->Ortho(0.0f, width-1, height-1, 0.0f, -1.0f, 1.0f); + glMatrixProject.Load(); + + glMatrixModview.Clear(); + glMatrixModview->LoadIdentity(); + glMatrixModview.Load(); + + glMatrixTexture.Clear(); + glMatrixTexture->LoadIdentity(); + glMatrixTexture.Load(); + + if (IsExtSupported("GL_ARB_multitexture")) + { + //clear error flags + ResetGLErrors(); + + GLint maxtex; + glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxtex); + + //some sanity checks + GLenum error = glGetError(); + if (error != GL_NO_ERROR) + { + CLog::Log(LOGERROR, "ResetRenderSystem() GL_MAX_TEXTURE_IMAGE_UNITS returned error {}", + (int)error); + maxtex = 3; + } + else if (maxtex < 1 || maxtex > 32) + { + CLog::Log(LOGERROR, + "ResetRenderSystem() GL_MAX_TEXTURE_IMAGE_UNITS returned invalid value {}", + (int)maxtex); + maxtex = 3; + } + + //reset texture matrix for all textures + for (GLint i = 0; i < maxtex; i++) + { + glActiveTexture(GL_TEXTURE0 + i); + glMatrixTexture.Load(); + } + glActiveTexture(GL_TEXTURE0); + } + + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + glEnable(GL_BLEND); // Turn Blending On + glDisable(GL_DEPTH_TEST); + + return true; +} + +bool CRenderSystemGL::DestroyRenderSystem() +{ + if (m_vertexArray != GL_NONE) + { + glDeleteVertexArrays(1, &m_vertexArray); + } + + ReleaseShaders(); + m_bRenderCreated = false; + + return true; +} + +bool CRenderSystemGL::BeginRender() +{ + if (!m_bRenderCreated) + return false; + + bool useLimited = CServiceBroker::GetWinSystem()->UseLimitedColor(); + + if (m_limitedColorRange != useLimited) + { + ReleaseShaders(); + InitialiseShaders(); + } + + m_limitedColorRange = useLimited; + return true; +} + +bool CRenderSystemGL::EndRender() +{ + if (!m_bRenderCreated) + return false; + + return true; +} + +bool CRenderSystemGL::ClearBuffers(UTILS::COLOR::Color color) +{ + if (!m_bRenderCreated) + return false; + + /* clear is not affected by stipple pattern, so we can only clear on first frame */ + if(m_stereoMode == RENDER_STEREO_MODE_INTERLACED && m_stereoView == RENDER_STEREO_VIEW_RIGHT) + return true; + + float r = KODI::UTILS::GL::GetChannelFromARGB(KODI::UTILS::GL::ColorChannel::R, color) / 255.0f; + float g = KODI::UTILS::GL::GetChannelFromARGB(KODI::UTILS::GL::ColorChannel::G, color) / 255.0f; + float b = KODI::UTILS::GL::GetChannelFromARGB(KODI::UTILS::GL::ColorChannel::B, color) / 255.0f; + float a = KODI::UTILS::GL::GetChannelFromARGB(KODI::UTILS::GL::ColorChannel::A, color) / 255.0f; + + glClearColor(r, g, b, a); + + GLbitfield flags = GL_COLOR_BUFFER_BIT; + glClear(flags); + + return true; +} + +bool CRenderSystemGL::IsExtSupported(const char* extension) const +{ + if (m_RenderVersionMajor > 3 || + (m_RenderVersionMajor == 3 && m_RenderVersionMinor >= 2)) + { + if (strcmp( extension, "GL_EXT_framebuffer_object") == 0) + { + return true; + } + if (strcmp( extension, "GL_ARB_texture_non_power_of_two") == 0) + { + return true; + } + } + + std::string name; + name = " "; + name += extension; + name += " "; + + return m_RenderExtensions.find(name) != std::string::npos; +} + +bool CRenderSystemGL::SupportsNPOT(bool dxt) const +{ + return true; +} + +void CRenderSystemGL::PresentRender(bool rendered, bool videoLayer) +{ + SetVSync(true); + + if (!m_bRenderCreated) + return; + + PresentRenderImpl(rendered); + + if (!rendered) + KODI::TIME::Sleep(40ms); +} + +void CRenderSystemGL::SetVSync(bool enable) +{ + if (m_bVSync == enable && m_bVsyncInit == true) + return; + + if (!m_bRenderCreated) + return; + + if (enable) + CLog::Log(LOGINFO, "GL: Enabling VSYNC"); + else + CLog::Log(LOGINFO, "GL: Disabling VSYNC"); + + m_bVSync = enable; + m_bVsyncInit = true; + + SetVSyncImpl(enable); +} + +void CRenderSystemGL::CaptureStateBlock() +{ + if (!m_bRenderCreated) + return; + + glMatrixProject.Push(); + glMatrixModview.Push(); + glMatrixTexture.Push(); + + glDisable(GL_SCISSOR_TEST); // fixes FBO corruption on Macs + glActiveTexture(GL_TEXTURE0); +} + +void CRenderSystemGL::ApplyStateBlock() +{ + if (!m_bRenderCreated) + return; + + glBindVertexArray(m_vertexArray); + + glViewport(m_viewPort[0], m_viewPort[1], m_viewPort[2], m_viewPort[3]); + + glMatrixProject.PopLoad(); + glMatrixModview.PopLoad(); + glMatrixTexture.PopLoad(); + + glActiveTexture(GL_TEXTURE0); + glEnable(GL_BLEND); + glEnable(GL_SCISSOR_TEST); +} + +void CRenderSystemGL::SetCameraPosition(const CPoint &camera, int screenWidth, int screenHeight, float stereoFactor) +{ + if (!m_bRenderCreated) + return; + + CPoint offset = camera - CPoint(screenWidth*0.5f, screenHeight*0.5f); + + + float w = (float)m_viewPort[2]*0.5f; + float h = (float)m_viewPort[3]*0.5f; + + glMatrixModview->LoadIdentity(); + glMatrixModview->Translatef(-(w + offset.x - stereoFactor), +(h + offset.y), 0); + glMatrixModview->LookAt(0.0f, 0.0f, -2.0f * h, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f); + glMatrixModview.Load(); + + glMatrixProject->LoadIdentity(); + glMatrixProject->Frustum( (-w - offset.x)*0.5f, (w - offset.x)*0.5f, (-h + offset.y)*0.5f, (h + offset.y)*0.5f, h, 100*h); + glMatrixProject.Load(); +} + +void CRenderSystemGL::Project(float &x, float &y, float &z) +{ + GLfloat coordX, coordY, coordZ; + if (CMatrixGL::Project(x, y, z, glMatrixModview.Get(), glMatrixProject.Get(), m_viewPort, &coordX, &coordY, &coordZ)) + { + x = coordX; + y = (float)(m_viewPort[1] + m_viewPort[3] - coordY); + z = 0; + } +} + +void CRenderSystemGL::CalculateMaxTexturesize() +{ + GLint width = 256; + + // reset any previous GL errors + ResetGLErrors(); + + // max out at 2^(8+8) + for (int i = 0 ; i<8 ; i++) + { + glTexImage2D(GL_PROXY_TEXTURE_2D, 0, GL_RGBA, width, width, 0, GL_BGRA, + GL_UNSIGNED_BYTE, NULL); + glGetTexLevelParameteriv(GL_PROXY_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, + &width); + + // GMA950 on OS X sets error instead + if (width == 0 || (glGetError() != GL_NO_ERROR) ) + break; + + m_maxTextureSize = width; + width *= 2; + if (width > 65536) // have an upper limit in case driver acts stupid + { + CLog::Log(LOGERROR, "GL: Could not determine maximum texture width, falling back to 2048"); + m_maxTextureSize = 2048; + break; + } + } + + CLog::Log(LOGINFO, "GL: Maximum texture width: {}", m_maxTextureSize); +} + +void CRenderSystemGL::GetViewPort(CRect& viewPort) +{ + if (!m_bRenderCreated) + return; + + viewPort.x1 = m_viewPort[0]; + viewPort.y1 = m_height - m_viewPort[1] - m_viewPort[3]; + viewPort.x2 = m_viewPort[0] + m_viewPort[2]; + viewPort.y2 = viewPort.y1 + m_viewPort[3]; +} + +void CRenderSystemGL::SetViewPort(const CRect& viewPort) +{ + if (!m_bRenderCreated) + return; + + glScissor((GLint) viewPort.x1, (GLint) (m_height - viewPort.y1 - viewPort.Height()), (GLsizei) viewPort.Width(), (GLsizei) viewPort.Height()); + glViewport((GLint) viewPort.x1, (GLint) (m_height - viewPort.y1 - viewPort.Height()), (GLsizei) viewPort.Width(), (GLsizei) viewPort.Height()); + m_viewPort[0] = viewPort.x1; + m_viewPort[1] = m_height - viewPort.y1 - viewPort.Height(); + m_viewPort[2] = viewPort.Width(); + m_viewPort[3] = viewPort.Height(); +} + +bool CRenderSystemGL::ScissorsCanEffectClipping() +{ + if (m_pShader[m_method]) + return m_pShader[m_method]->HardwareClipIsPossible(); + + return false; +} + +CRect CRenderSystemGL::ClipRectToScissorRect(const CRect &rect) +{ + if (!m_pShader[m_method]) + return CRect(); + float xFactor = m_pShader[m_method]->GetClipXFactor(); + float xOffset = m_pShader[m_method]->GetClipXOffset(); + float yFactor = m_pShader[m_method]->GetClipYFactor(); + float yOffset = m_pShader[m_method]->GetClipYOffset(); + return CRect(rect.x1 * xFactor + xOffset, + rect.y1 * yFactor + yOffset, + rect.x2 * xFactor + xOffset, + rect.y2 * yFactor + yOffset); +} + +void CRenderSystemGL::SetScissors(const CRect &rect) +{ + if (!m_bRenderCreated) + return; + GLint x1 = MathUtils::round_int(static_cast<double>(rect.x1)); + GLint y1 = MathUtils::round_int(static_cast<double>(rect.y1)); + GLint x2 = MathUtils::round_int(static_cast<double>(rect.x2)); + GLint y2 = MathUtils::round_int(static_cast<double>(rect.y2)); + glScissor(x1, m_height - y2, x2-x1, y2-y1); +} + +void CRenderSystemGL::ResetScissors() +{ + SetScissors(CRect(0, 0, (float)m_width, (float)m_height)); +} + +void CRenderSystemGL::GetGLSLVersion(int& major, int& minor) +{ + major = m_glslMajor; + minor = m_glslMinor; +} + +void CRenderSystemGL::ResetGLErrors() +{ + int count = 0; + while (glGetError() != GL_NO_ERROR) + { + count++; + if (count >= 100) + { + CLog::Log( + LOGWARNING, + "CRenderSystemGL::ResetGLErrors glGetError didn't return GL_NO_ERROR after {} iterations", + count); + break; + } + } +} +static const GLubyte stipple_3d[] = { + 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, +}; + +void CRenderSystemGL::SetStereoMode(RENDER_STEREO_MODE mode, RENDER_STEREO_VIEW view) +{ + CRenderSystemBase::SetStereoMode(mode, view); + + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glDrawBuffer(GL_BACK); + + if(m_stereoMode == RENDER_STEREO_MODE_ANAGLYPH_RED_CYAN) + { + if(m_stereoView == RENDER_STEREO_VIEW_LEFT) + glColorMask(GL_TRUE, GL_FALSE, GL_FALSE, GL_TRUE); + else if(m_stereoView == RENDER_STEREO_VIEW_RIGHT) + glColorMask(GL_FALSE, GL_TRUE, GL_TRUE, GL_TRUE); + } + if(m_stereoMode == RENDER_STEREO_MODE_ANAGLYPH_GREEN_MAGENTA) + { + if(m_stereoView == RENDER_STEREO_VIEW_LEFT) + glColorMask(GL_FALSE, GL_TRUE, GL_FALSE, GL_TRUE); + else if(m_stereoView == RENDER_STEREO_VIEW_RIGHT) + glColorMask(GL_TRUE, GL_FALSE, GL_TRUE, GL_TRUE); + } + if(m_stereoMode == RENDER_STEREO_MODE_ANAGLYPH_YELLOW_BLUE) + { + if(m_stereoView == RENDER_STEREO_VIEW_LEFT) + glColorMask(GL_TRUE, GL_TRUE, GL_FALSE, GL_TRUE); + else if(m_stereoView == RENDER_STEREO_VIEW_RIGHT) + glColorMask(GL_FALSE, GL_FALSE, GL_TRUE, GL_TRUE); + } + + if(m_stereoMode == RENDER_STEREO_MODE_INTERLACED) + { + glEnable(GL_POLYGON_STIPPLE); + if(m_stereoView == RENDER_STEREO_VIEW_LEFT) + glPolygonStipple(stipple_3d); + else if(m_stereoView == RENDER_STEREO_VIEW_RIGHT) + glPolygonStipple(stipple_3d+4); + } + + if(m_stereoMode == RENDER_STEREO_MODE_HARDWAREBASED) + { + if(m_stereoView == RENDER_STEREO_VIEW_LEFT) + glDrawBuffer(GL_BACK_LEFT); + else if(m_stereoView == RENDER_STEREO_VIEW_RIGHT) + glDrawBuffer(GL_BACK_RIGHT); + } + +} + +bool CRenderSystemGL::SupportsStereo(RENDER_STEREO_MODE mode) const +{ + switch(mode) + { + case RENDER_STEREO_MODE_ANAGLYPH_RED_CYAN: + case RENDER_STEREO_MODE_ANAGLYPH_GREEN_MAGENTA: + case RENDER_STEREO_MODE_ANAGLYPH_YELLOW_BLUE: + case RENDER_STEREO_MODE_INTERLACED: + return true; + case RENDER_STEREO_MODE_HARDWAREBASED: { + //This is called by setting init, at which point GL is not inited + //luckily if GL doesn't support this, it will just behave as if + //it was not in effect. + //GLboolean stereo = GL_FALSE; + //glGetBooleanv(GL_STEREO, &stereo); + //return stereo == GL_TRUE ? true : false; + return true; + } + default: + return CRenderSystemBase::SupportsStereo(mode); + } +} + +// ----------------------------------------------------------------------------- +// shaders +// ----------------------------------------------------------------------------- +void CRenderSystemGL::InitialiseShaders() +{ + std::string defines; + m_limitedColorRange = CServiceBroker::GetWinSystem()->UseLimitedColor(); + if (m_limitedColorRange) + { + defines += "#define KODI_LIMITED_RANGE 1\n"; + } + + m_pShader[ShaderMethodGL::SM_DEFAULT] = std::make_unique<CGLShader>( + "gl_shader_vert_default.glsl", "gl_shader_frag_default.glsl", defines); + if (!m_pShader[ShaderMethodGL::SM_DEFAULT]->CompileAndLink()) + { + m_pShader[ShaderMethodGL::SM_DEFAULT]->Free(); + m_pShader[ShaderMethodGL::SM_DEFAULT].reset(); + CLog::Log(LOGERROR, "GUI Shader gl_shader_frag_default.glsl - compile and link failed"); + } + + m_pShader[ShaderMethodGL::SM_TEXTURE] = + std::make_unique<CGLShader>("gl_shader_frag_texture.glsl", defines); + if (!m_pShader[ShaderMethodGL::SM_TEXTURE]->CompileAndLink()) + { + m_pShader[ShaderMethodGL::SM_TEXTURE]->Free(); + m_pShader[ShaderMethodGL::SM_TEXTURE].reset(); + CLog::Log(LOGERROR, "GUI Shader gl_shader_frag_texture.glsl - compile and link failed"); + } + + m_pShader[ShaderMethodGL::SM_TEXTURE_LIM] = + std::make_unique<CGLShader>("gl_shader_frag_texture_lim.glsl", defines); + if (!m_pShader[ShaderMethodGL::SM_TEXTURE_LIM]->CompileAndLink()) + { + m_pShader[ShaderMethodGL::SM_TEXTURE_LIM]->Free(); + m_pShader[ShaderMethodGL::SM_TEXTURE_LIM].reset(); + CLog::Log(LOGERROR, "GUI Shader gl_shader_frag_texture_lim.glsl - compile and link failed"); + } + + m_pShader[ShaderMethodGL::SM_MULTI] = + std::make_unique<CGLShader>("gl_shader_frag_multi.glsl", defines); + if (!m_pShader[ShaderMethodGL::SM_MULTI]->CompileAndLink()) + { + m_pShader[ShaderMethodGL::SM_MULTI]->Free(); + m_pShader[ShaderMethodGL::SM_MULTI].reset(); + CLog::Log(LOGERROR, "GUI Shader gl_shader_frag_multi.glsl - compile and link failed"); + } + + m_pShader[ShaderMethodGL::SM_FONTS] = + std::make_unique<CGLShader>("gl_shader_frag_fonts.glsl", defines); + if (!m_pShader[ShaderMethodGL::SM_FONTS]->CompileAndLink()) + { + m_pShader[ShaderMethodGL::SM_FONTS]->Free(); + m_pShader[ShaderMethodGL::SM_FONTS].reset(); + CLog::Log(LOGERROR, "GUI Shader gl_shader_frag_fonts.glsl - compile and link failed"); + } + + m_pShader[ShaderMethodGL::SM_TEXTURE_NOBLEND] = + std::make_unique<CGLShader>("gl_shader_frag_texture_noblend.glsl", defines); + if (!m_pShader[ShaderMethodGL::SM_TEXTURE_NOBLEND]->CompileAndLink()) + { + m_pShader[ShaderMethodGL::SM_TEXTURE_NOBLEND]->Free(); + m_pShader[ShaderMethodGL::SM_TEXTURE_NOBLEND].reset(); + CLog::Log(LOGERROR, "GUI Shader gl_shader_frag_texture_noblend.glsl - compile and link failed"); + } + + m_pShader[ShaderMethodGL::SM_MULTI_BLENDCOLOR] = + std::make_unique<CGLShader>("gl_shader_frag_multi_blendcolor.glsl", defines); + if (!m_pShader[ShaderMethodGL::SM_MULTI_BLENDCOLOR]->CompileAndLink()) + { + m_pShader[ShaderMethodGL::SM_MULTI_BLENDCOLOR]->Free(); + m_pShader[ShaderMethodGL::SM_MULTI_BLENDCOLOR].reset(); + CLog::Log(LOGERROR, "GUI Shader gl_shader_frag_multi_blendcolor.glsl - compile and link failed"); + } +} + +void CRenderSystemGL::ReleaseShaders() +{ + if (m_pShader[ShaderMethodGL::SM_DEFAULT]) + m_pShader[ShaderMethodGL::SM_DEFAULT]->Free(); + m_pShader[ShaderMethodGL::SM_DEFAULT].reset(); + + if (m_pShader[ShaderMethodGL::SM_TEXTURE]) + m_pShader[ShaderMethodGL::SM_TEXTURE]->Free(); + m_pShader[ShaderMethodGL::SM_TEXTURE].reset(); + + if (m_pShader[ShaderMethodGL::SM_TEXTURE_LIM]) + m_pShader[ShaderMethodGL::SM_TEXTURE_LIM]->Free(); + m_pShader[ShaderMethodGL::SM_TEXTURE_LIM].reset(); + + if (m_pShader[ShaderMethodGL::SM_MULTI]) + m_pShader[ShaderMethodGL::SM_MULTI]->Free(); + m_pShader[ShaderMethodGL::SM_MULTI].reset(); + + if (m_pShader[ShaderMethodGL::SM_FONTS]) + m_pShader[ShaderMethodGL::SM_FONTS]->Free(); + m_pShader[ShaderMethodGL::SM_FONTS].reset(); + + if (m_pShader[ShaderMethodGL::SM_TEXTURE_NOBLEND]) + m_pShader[ShaderMethodGL::SM_TEXTURE_NOBLEND]->Free(); + m_pShader[ShaderMethodGL::SM_TEXTURE_NOBLEND].reset(); + + if (m_pShader[ShaderMethodGL::SM_MULTI_BLENDCOLOR]) + m_pShader[ShaderMethodGL::SM_MULTI_BLENDCOLOR]->Free(); + m_pShader[ShaderMethodGL::SM_MULTI_BLENDCOLOR].reset(); +} + +void CRenderSystemGL::EnableShader(ShaderMethodGL method) +{ + m_method = method; + if (m_pShader[m_method]) + { + m_pShader[m_method]->Enable(); + } + else + { + CLog::Log(LOGERROR, "Invalid GUI Shader selected {}", method); + } +} + +void CRenderSystemGL::DisableShader() +{ + if (m_pShader[m_method]) + { + m_pShader[m_method]->Disable(); + } + m_method = ShaderMethodGL::SM_DEFAULT; +} + +GLint CRenderSystemGL::ShaderGetPos() +{ + if (m_pShader[m_method]) + return m_pShader[m_method]->GetPosLoc(); + + return -1; +} + +GLint CRenderSystemGL::ShaderGetCol() +{ + if (m_pShader[m_method]) + return m_pShader[m_method]->GetColLoc(); + + return -1; +} + +GLint CRenderSystemGL::ShaderGetCoord0() +{ + if (m_pShader[m_method]) + return m_pShader[m_method]->GetCord0Loc(); + + return -1; +} + +GLint CRenderSystemGL::ShaderGetCoord1() +{ + if (m_pShader[m_method]) + return m_pShader[m_method]->GetCord1Loc(); + + return -1; +} + +GLint CRenderSystemGL::ShaderGetUniCol() +{ + if (m_pShader[m_method]) + return m_pShader[m_method]->GetUniColLoc(); + + return -1; +} + +GLint CRenderSystemGL::ShaderGetModel() +{ + if (m_pShader[m_method]) + return m_pShader[m_method]->GetModelLoc(); + + return -1; +} + +std::string CRenderSystemGL::GetShaderPath(const std::string &filename) +{ + std::string path = "GL/1.2/"; + + if (m_glslMajor >= 4) + { + std::string file = "special://xbmc/system/shaders/GL/4.0/" + filename; + const CURL pathToUrl(file); + if (CFileUtils::Exists(pathToUrl.Get())) + return "GL/4.0/"; + } + if (m_glslMajor >= 2 || (m_glslMajor == 1 && m_glslMinor >= 50)) + path = "GL/1.5/"; + + return path; +} diff --git a/xbmc/rendering/gl/RenderSystemGL.h b/xbmc/rendering/gl/RenderSystemGL.h new file mode 100644 index 0000000..191c97f --- /dev/null +++ b/xbmc/rendering/gl/RenderSystemGL.h @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2005-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 "GLShader.h" +#include "rendering/RenderSystem.h" +#include "utils/ColorUtils.h" +#include "utils/Map.h" + +#include <map> +#include <memory> + +#include <fmt/format.h> + +#include "system_gl.h" + +enum class ShaderMethodGL +{ + SM_DEFAULT = 0, + SM_TEXTURE, + SM_TEXTURE_LIM, + SM_MULTI, + SM_FONTS, + SM_TEXTURE_NOBLEND, + SM_MULTI_BLENDCOLOR, + SM_MAX +}; + +template<> +struct fmt::formatter<ShaderMethodGL> : fmt::formatter<std::string_view> +{ + template<typename FormatContext> + constexpr auto format(const ShaderMethodGL& shaderMethod, FormatContext& ctx) + { + const auto it = ShaderMethodGLMap.find(shaderMethod); + if (it == ShaderMethodGLMap.cend()) + throw std::range_error("no string mapping found for shader method"); + + return fmt::formatter<string_view>::format(it->second, ctx); + } + +private: + static constexpr auto ShaderMethodGLMap = make_map<ShaderMethodGL, std::string_view>({ + {ShaderMethodGL::SM_DEFAULT, "default"}, + {ShaderMethodGL::SM_TEXTURE, "texture"}, + {ShaderMethodGL::SM_TEXTURE_LIM, "texture limited"}, + {ShaderMethodGL::SM_MULTI, "multi"}, + {ShaderMethodGL::SM_FONTS, "fonts"}, + {ShaderMethodGL::SM_TEXTURE_NOBLEND, "texture no blending"}, + {ShaderMethodGL::SM_MULTI_BLENDCOLOR, "multi blend colour"}, + }); + + static_assert(static_cast<size_t>(ShaderMethodGL::SM_MAX) == ShaderMethodGLMap.size(), + "ShaderMethodGLMap doesn't match the size of ShaderMethodGL, did you forget to " + "add/remove a mapping?"); +}; + +class CRenderSystemGL : public CRenderSystemBase +{ +public: + CRenderSystemGL(); + ~CRenderSystemGL() override; + bool InitRenderSystem() override; + bool DestroyRenderSystem() override; + bool ResetRenderSystem(int width, int height) override; + + bool BeginRender() override; + bool EndRender() override; + void PresentRender(bool rendered, bool videoLayer) override; + bool ClearBuffers(UTILS::COLOR::Color color) override; + bool IsExtSupported(const char* extension) const override; + + void SetVSync(bool vsync); + void ResetVSync() { m_bVsyncInit = false; } + + void SetViewPort(const CRect& viewPort) override; + void GetViewPort(CRect& viewPort) override; + + bool ScissorsCanEffectClipping() override; + CRect ClipRectToScissorRect(const CRect &rect) override; + void SetScissors(const CRect &rect) override; + void ResetScissors() override; + + void CaptureStateBlock() override; + void ApplyStateBlock() override; + + void SetCameraPosition(const CPoint &camera, int screenWidth, int screenHeight, float stereoFactor = 0.0f) override; + + void SetStereoMode(RENDER_STEREO_MODE mode, RENDER_STEREO_VIEW view) override; + bool SupportsStereo(RENDER_STEREO_MODE mode) const override; + bool SupportsNPOT(bool dxt) const override; + + void Project(float &x, float &y, float &z) override; + + std::string GetShaderPath(const std::string &filename) override; + + void GetGLVersion(int& major, int& minor); + void GetGLSLVersion(int& major, int& minor); + + void ResetGLErrors(); + + // shaders + void EnableShader(ShaderMethodGL method); + void DisableShader(); + GLint ShaderGetPos(); + GLint ShaderGetCol(); + GLint ShaderGetCoord0(); + GLint ShaderGetCoord1(); + GLint ShaderGetUniCol(); + GLint ShaderGetModel(); + +protected: + virtual void SetVSyncImpl(bool enable) = 0; + virtual void PresentRenderImpl(bool rendered) = 0; + void CalculateMaxTexturesize(); + void InitialiseShaders(); + void ReleaseShaders(); + + bool m_bVsyncInit = false; + int m_width; + int m_height; + + std::string m_RenderExtensions; + + int m_glslMajor = 0; + int m_glslMinor = 0; + + GLint m_viewPort[4]; + + std::map<ShaderMethodGL, std::unique_ptr<CGLShader>> m_pShader; + ShaderMethodGL m_method = ShaderMethodGL::SM_DEFAULT; + GLuint m_vertexArray = GL_NONE; +}; diff --git a/xbmc/rendering/gl/ScreenshotSurfaceGL.cpp b/xbmc/rendering/gl/ScreenshotSurfaceGL.cpp new file mode 100644 index 0000000..022611b --- /dev/null +++ b/xbmc/rendering/gl/ScreenshotSurfaceGL.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2005-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 "ScreenshotSurfaceGL.h" + +#include "ServiceBroker.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "utils/Screenshot.h" +#include "windowing/GraphicContext.h" + +#include <mutex> +#include <vector> + +#include "system_gl.h" + +void CScreenshotSurfaceGL::Register() +{ + CScreenShot::Register(CScreenshotSurfaceGL::CreateSurface); +} + +std::unique_ptr<IScreenshotSurface> CScreenshotSurfaceGL::CreateSurface() +{ + return std::unique_ptr<CScreenshotSurfaceGL>(new CScreenshotSurfaceGL()); +} + +bool CScreenshotSurfaceGL::Capture() +{ + CWinSystemBase* winsystem = CServiceBroker::GetWinSystem(); + if (!winsystem) + return false; + + CGUIComponent* gui = CServiceBroker::GetGUI(); + if (!gui) + return false; + + std::unique_lock<CCriticalSection> lock(winsystem->GetGfxContext()); + gui->GetWindowManager().Render(); + + glReadBuffer(GL_BACK); + + // get current viewport + GLint viewport[4]; + glGetIntegerv(GL_VIEWPORT, viewport); + + m_width = viewport[2] - viewport[0]; + m_height = viewport[3] - viewport[1]; + m_stride = m_width * 4; + std::vector<uint8_t> surface(m_stride * m_height); + + // read pixels from the backbuffer + glReadPixels(viewport[0], viewport[1], viewport[2], viewport[3], GL_BGRA, GL_UNSIGNED_BYTE, static_cast<GLvoid*>(surface.data())); + + // make a new buffer and copy the read image to it with the Y axis inverted + m_buffer = new unsigned char[m_stride * m_height]; + for (int y = 0; y < m_height; y++) + memcpy(m_buffer + y * m_stride, surface.data() + (m_height - y - 1) * m_stride, m_stride); + + return m_buffer != nullptr; +} diff --git a/xbmc/rendering/gl/ScreenshotSurfaceGL.h b/xbmc/rendering/gl/ScreenshotSurfaceGL.h new file mode 100644 index 0000000..01f0590 --- /dev/null +++ b/xbmc/rendering/gl/ScreenshotSurfaceGL.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2005-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 "utils/IScreenshotSurface.h" + +#include <memory> + +class CScreenshotSurfaceGL : public IScreenshotSurface +{ +public: + static void Register(); + static std::unique_ptr<IScreenshotSurface> CreateSurface(); + + bool Capture() override; +}; diff --git a/xbmc/rendering/gles/CMakeLists.txt b/xbmc/rendering/gles/CMakeLists.txt new file mode 100644 index 0000000..74cfe63 --- /dev/null +++ b/xbmc/rendering/gles/CMakeLists.txt @@ -0,0 +1,11 @@ +if(OPENGLES_FOUND) + set(SOURCES RenderSystemGLES.cpp + ScreenshotSurfaceGLES.cpp + GLESShader.cpp) + + set(HEADERS RenderSystemGLES.h + ScreenshotSurfaceGLES.h + GLESShader.h) + + core_add_library(rendering_gles) +endif() diff --git a/xbmc/rendering/gles/GLESShader.cpp b/xbmc/rendering/gles/GLESShader.cpp new file mode 100644 index 0000000..3964f4b --- /dev/null +++ b/xbmc/rendering/gles/GLESShader.cpp @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2005-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 "GLESShader.h" + +#include "ServiceBroker.h" +#include "rendering/MatrixGL.h" +#include "rendering/RenderSystem.h" +#include "utils/log.h" +#include "windowing/GraphicContext.h" + +using namespace Shaders; + +CGLESShader::CGLESShader(const char* shader, const std::string& prefix) +{ + m_proj = nullptr; + m_model = nullptr; + m_clipPossible = false; + + VertexShader()->LoadSource("gles_shader.vert"); + PixelShader()->LoadSource(shader, prefix); +} + +CGLESShader::CGLESShader(const char* vshader, const char* fshader, const std::string& prefix) +{ + m_proj = nullptr; + m_model = nullptr; + m_clipPossible = false; + + VertexShader()->LoadSource(vshader, prefix); + PixelShader()->LoadSource(fshader, prefix); +} + +void CGLESShader::OnCompiledAndLinked() +{ + // This is called after CompileAndLink() + + // Variables passed directly to the Fragment shader + m_hTex0 = glGetUniformLocation(ProgramHandle(), "m_samp0"); + m_hTex1 = glGetUniformLocation(ProgramHandle(), "m_samp1"); + m_hUniCol = glGetUniformLocation(ProgramHandle(), "m_unicol"); + m_hField = glGetUniformLocation(ProgramHandle(), "m_field"); + m_hStep = glGetUniformLocation(ProgramHandle(), "m_step"); + m_hContrast = glGetUniformLocation(ProgramHandle(), "m_contrast"); + m_hBrightness = glGetUniformLocation(ProgramHandle(), "m_brightness"); + + // Variables passed directly to the Vertex shader + m_hProj = glGetUniformLocation(ProgramHandle(), "m_proj"); + m_hModel = glGetUniformLocation(ProgramHandle(), "m_model"); + m_hCoord0Matrix = glGetUniformLocation(ProgramHandle(), "m_coord0Matrix"); + + // Vertex attributes + m_hPos = glGetAttribLocation(ProgramHandle(), "m_attrpos"); + m_hCol = glGetAttribLocation(ProgramHandle(), "m_attrcol"); + m_hCord0 = glGetAttribLocation(ProgramHandle(), "m_attrcord0"); + m_hCord1 = glGetAttribLocation(ProgramHandle(), "m_attrcord1"); + + // It's okay to do this only one time. Textures units never change. + glUseProgram( ProgramHandle() ); + glUniform1i(m_hTex0, 0); + glUniform1i(m_hTex1, 1); + glUniform4f(m_hUniCol, 1.0, 1.0, 1.0, 1.0); + + const float identity[16] = { + 1.0f, 0.0f, 0.0f, 0.0f, + 0.0f, 1.0f, 0.0f, 0.0f, + 0.0f, 0.0f, 1.0f, 0.0f, + 0.0f, 0.0f, 0.0f, 1.0f + }; + glUniformMatrix4fv(m_hCoord0Matrix, 1, GL_FALSE, identity); + + glUseProgram( 0 ); +} + +bool CGLESShader::OnEnabled() +{ + // This is called after glUseProgram() + + const GLfloat *projMatrix = glMatrixProject.Get(); + const GLfloat *modelMatrix = glMatrixModview.Get(); + glUniformMatrix4fv(m_hProj, 1, GL_FALSE, projMatrix); + glUniformMatrix4fv(m_hModel, 1, GL_FALSE, modelMatrix); + + const TransformMatrix &guiMatrix = CServiceBroker::GetWinSystem()->GetGfxContext().GetGUIMatrix(); + CRect viewPort; // absolute positions of corners + CServiceBroker::GetRenderSystem()->GetViewPort(viewPort); + + /* glScissor operates in window coordinates. In order that we can use it to + * perform clipping, we must ensure that there is an independent linear + * transformation from the coordinate system used by CGraphicContext::ClipRect + * to window coordinates, separately for X and Y (in other words, no + * rotation or shear is introduced at any stage). To do, this, we need to + * check that zeros are present in the following locations: + * + * GUI matrix: + * / * 0 * * \ + * | 0 * * * | + * \ 0 0 * * / + * ^ TransformMatrix::TransformX/Y/ZCoord are only ever called with + * input z = 0, so this column doesn't matter + * Model-view matrix: + * / * 0 0 * \ + * | 0 * 0 * | + * | 0 0 * * | + * \ * * * * / <- eye w has no influence on window x/y (last column below + * is either 0 or ignored) + * Projection matrix: + * / * 0 0 0 \ + * | 0 * 0 0 | + * | * * * * | <- normalised device coordinate z has no influence on window x/y + * \ 0 0 * 0 / + * + * Some of these zeros are not strictly required to ensure this, but they tend + * to be zeroed in the common case, so by checking for zeros here, we simplify + * the calculation of the window x/y coordinates further down the line. + * + * (Minor detail: we don't quite deal in window coordinates as defined by + * OpenGL, because CRenderSystemGLES::SetScissors flips the Y axis. But all + * that's needed to handle that is an effective negation at the stage where + * Y is in normalised device coordinates.) + */ + m_clipPossible = guiMatrix.m[0][1] == 0 && + guiMatrix.m[1][0] == 0 && + guiMatrix.m[2][0] == 0 && + guiMatrix.m[2][1] == 0 && + modelMatrix[0+1*4] == 0 && + modelMatrix[0+2*4] == 0 && + modelMatrix[1+0*4] == 0 && + modelMatrix[1+2*4] == 0 && + modelMatrix[2+0*4] == 0 && + modelMatrix[2+1*4] == 0 && + projMatrix[0+1*4] == 0 && + projMatrix[0+2*4] == 0 && + projMatrix[0+3*4] == 0 && + projMatrix[1+0*4] == 0 && + projMatrix[1+2*4] == 0 && + projMatrix[1+3*4] == 0 && + projMatrix[3+0*4] == 0 && + projMatrix[3+1*4] == 0 && + projMatrix[3+3*4] == 0; + + m_clipXFactor = 0.0; + m_clipXOffset = 0.0; + m_clipYFactor = 0.0; + m_clipYOffset = 0.0; + + if (m_clipPossible) + { + m_clipXFactor = guiMatrix.m[0][0] * modelMatrix[0+0*4] * projMatrix[0+0*4]; + m_clipXOffset = (guiMatrix.m[0][3] * modelMatrix[0+0*4] + modelMatrix[0+3*4]) * projMatrix[0+0*4]; + m_clipYFactor = guiMatrix.m[1][1] * modelMatrix[1+1*4] * projMatrix[1+1*4]; + m_clipYOffset = (guiMatrix.m[1][3] * modelMatrix[1+1*4] + modelMatrix[1+3*4]) * projMatrix[1+1*4]; + float clipW = (guiMatrix.m[2][3] * modelMatrix[2+2*4] + modelMatrix[2+3*4]) * projMatrix[3+2*4]; + float xMult = (viewPort.x2 - viewPort.x1) / (2 * clipW); + float yMult = (viewPort.y1 - viewPort.y2) / (2 * clipW); // correct for inverted window coordinate scheme + m_clipXFactor = m_clipXFactor * xMult; + m_clipXOffset = m_clipXOffset * xMult + (viewPort.x2 + viewPort.x1) / 2; + m_clipYFactor = m_clipYFactor * yMult; + m_clipYOffset = m_clipYOffset * yMult + (viewPort.y2 + viewPort.y1) / 2; + } + + glUniform1f(m_hBrightness, 0.0f); + glUniform1f(m_hContrast, 1.0f); + + return true; +} + +void CGLESShader::Free() +{ + // Do Cleanup here + CGLSLShaderProgram::Free(); +} + diff --git a/xbmc/rendering/gles/GLESShader.h b/xbmc/rendering/gles/GLESShader.h new file mode 100644 index 0000000..8ce31e5 --- /dev/null +++ b/xbmc/rendering/gles/GLESShader.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2005-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 "guilib/Shader.h" + +#include <string> + +class CGLESShader : public Shaders::CGLSLShaderProgram +{ +public: + CGLESShader(const char* shader, const std::string& prefix); + CGLESShader(const char* vshader, const char* fshader, const std::string& prefix); + void OnCompiledAndLinked() override; + bool OnEnabled() override; + void Free(); + + GLint GetPosLoc() { return m_hPos; } + GLint GetColLoc() { return m_hCol; } + GLint GetCord0Loc() { return m_hCord0; } + GLint GetCord1Loc() { return m_hCord1; } + GLint GetUniColLoc() { return m_hUniCol; } + GLint GetCoord0MatrixLoc() { return m_hCoord0Matrix; } + GLint GetFieldLoc() { return m_hField; } + GLint GetStepLoc() { return m_hStep; } + GLint GetContrastLoc() { return m_hContrast; } + GLint GetBrightnessLoc() { return m_hBrightness; } + GLint GetModelLoc() { return m_hModel; } + bool HardwareClipIsPossible() { return m_clipPossible; } + GLfloat GetClipXFactor() { return m_clipXFactor; } + GLfloat GetClipXOffset() { return m_clipXOffset; } + GLfloat GetClipYFactor() { return m_clipYFactor; } + GLfloat GetClipYOffset() { return m_clipYOffset; } + +protected: + GLint m_hTex0 = 0; + GLint m_hTex1 = 0; + GLint m_hUniCol = 0; + GLint m_hProj = 0; + GLint m_hModel = 0; + GLint m_hPos = 0; + GLint m_hCol = 0; + GLint m_hCord0 = 0; + GLint m_hCord1 = 0; + GLint m_hCoord0Matrix = 0; + GLint m_hField = 0; + GLint m_hStep = 0; + GLint m_hContrast = 0; + GLint m_hBrightness = 0; + + const GLfloat *m_proj; + const GLfloat *m_model; + + bool m_clipPossible; + GLfloat m_clipXFactor; + GLfloat m_clipXOffset; + GLfloat m_clipYFactor; + GLfloat m_clipYOffset; +}; diff --git a/xbmc/rendering/gles/RenderSystemGLES.cpp b/xbmc/rendering/gles/RenderSystemGLES.cpp new file mode 100644 index 0000000..d8557ff --- /dev/null +++ b/xbmc/rendering/gles/RenderSystemGLES.cpp @@ -0,0 +1,669 @@ +/* + * Copyright (C) 2005-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 "RenderSystemGLES.h" + +#include "guilib/DirtyRegion.h" +#include "guilib/GUITextureGLES.h" +#include "rendering/MatrixGL.h" +#include "settings/AdvancedSettings.h" +#include "settings/SettingsComponent.h" +#include "utils/GLUtils.h" +#include "utils/MathUtils.h" +#include "utils/SystemInfo.h" +#include "utils/TimeUtils.h" +#include "utils/XTimeUtils.h" +#include "utils/log.h" +#include "windowing/GraphicContext.h" + +#if defined(TARGET_LINUX) +#include "utils/EGLUtils.h" +#endif + +using namespace std::chrono_literals; + +CRenderSystemGLES::CRenderSystemGLES() + : CRenderSystemBase() +{ +} + +bool CRenderSystemGLES::InitRenderSystem() +{ + GLint maxTextureSize; + + glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize); + + m_maxTextureSize = maxTextureSize; + + // Get the GLES version number + m_RenderVersionMajor = 0; + m_RenderVersionMinor = 0; + + const char* ver = (const char*)glGetString(GL_VERSION); + if (ver != 0) + { + sscanf(ver, "%d.%d", &m_RenderVersionMajor, &m_RenderVersionMinor); + if (!m_RenderVersionMajor) + sscanf(ver, "%*s %*s %d.%d", &m_RenderVersionMajor, &m_RenderVersionMinor); + m_RenderVersion = ver; + } + + // Get our driver vendor and renderer + const char *tmpVendor = (const char*) glGetString(GL_VENDOR); + m_RenderVendor.clear(); + if (tmpVendor != NULL) + m_RenderVendor = tmpVendor; + + const char *tmpRenderer = (const char*) glGetString(GL_RENDERER); + m_RenderRenderer.clear(); + if (tmpRenderer != NULL) + m_RenderRenderer = tmpRenderer; + + m_RenderExtensions = " "; + + const char *tmpExtensions = (const char*) glGetString(GL_EXTENSIONS); + if (tmpExtensions != NULL) + { + m_RenderExtensions += tmpExtensions; + } + + m_RenderExtensions += " "; + +#if defined(GL_KHR_debug) && defined(TARGET_LINUX) + if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_openGlDebugging) + { + if (IsExtSupported("GL_KHR_debug")) + { + auto glDebugMessageCallback = CEGLUtils::GetRequiredProcAddress<PFNGLDEBUGMESSAGECALLBACKKHRPROC>("glDebugMessageCallbackKHR"); + auto glDebugMessageControl = CEGLUtils::GetRequiredProcAddress<PFNGLDEBUGMESSAGECONTROLKHRPROC>("glDebugMessageControlKHR"); + + glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS_KHR); + glDebugMessageCallback(KODI::UTILS::GL::GlErrorCallback, nullptr); + + // ignore shader compilation information + glDebugMessageControl(GL_DEBUG_SOURCE_SHADER_COMPILER_KHR, GL_DEBUG_TYPE_OTHER_KHR, GL_DONT_CARE, 0, nullptr, GL_FALSE); + + CLog::Log(LOGDEBUG, "OpenGL(ES): debugging enabled"); + } + else + { + CLog::Log(LOGDEBUG, "OpenGL(ES): debugging requested but the required extension isn't available (GL_KHR_debug)"); + } + } +#endif + + LogGraphicsInfo(); + + m_bRenderCreated = true; + + InitialiseShaders(); + + CGUITextureGLES::Register(); + + return true; +} + +bool CRenderSystemGLES::ResetRenderSystem(int width, int height) +{ + m_width = width; + m_height = height; + + glClearColor( 0.0f, 0.0f, 0.0f, 0.0f ); + CalculateMaxTexturesize(); + + CRect rect( 0, 0, width, height ); + SetViewPort( rect ); + + glEnable(GL_SCISSOR_TEST); + + glMatrixProject.Clear(); + glMatrixProject->LoadIdentity(); + glMatrixProject->Ortho(0.0f, width-1, height-1, 0.0f, -1.0f, 1.0f); + glMatrixProject.Load(); + + glMatrixModview.Clear(); + glMatrixModview->LoadIdentity(); + glMatrixModview.Load(); + + glMatrixTexture.Clear(); + glMatrixTexture->LoadIdentity(); + glMatrixTexture.Load(); + + glBlendFunc(GL_SRC_ALPHA, GL_ONE); + glEnable(GL_BLEND); // Turn Blending On + glDisable(GL_DEPTH_TEST); + + return true; +} + +bool CRenderSystemGLES::DestroyRenderSystem() +{ + ResetScissors(); + CDirtyRegionList dirtyRegions; + CDirtyRegion dirtyWindow(CServiceBroker::GetWinSystem()->GetGfxContext().GetViewWindow()); + dirtyRegions.push_back(dirtyWindow); + + ClearBuffers(0); + glFinish(); + PresentRenderImpl(true); + + ReleaseShaders(); + m_bRenderCreated = false; + + return true; +} + +bool CRenderSystemGLES::BeginRender() +{ + if (!m_bRenderCreated) + return false; + + bool useLimited = CServiceBroker::GetWinSystem()->UseLimitedColor(); + + if (m_limitedColorRange != useLimited) + { + ReleaseShaders(); + InitialiseShaders(); + } + + m_limitedColorRange = useLimited; + + return true; +} + +bool CRenderSystemGLES::EndRender() +{ + if (!m_bRenderCreated) + return false; + + return true; +} + +bool CRenderSystemGLES::ClearBuffers(UTILS::COLOR::Color color) +{ + if (!m_bRenderCreated) + return false; + + float r = KODI::UTILS::GL::GetChannelFromARGB(KODI::UTILS::GL::ColorChannel::R, color) / 255.0f; + float g = KODI::UTILS::GL::GetChannelFromARGB(KODI::UTILS::GL::ColorChannel::G, color) / 255.0f; + float b = KODI::UTILS::GL::GetChannelFromARGB(KODI::UTILS::GL::ColorChannel::B, color) / 255.0f; + float a = KODI::UTILS::GL::GetChannelFromARGB(KODI::UTILS::GL::ColorChannel::A, color) / 255.0f; + + glClearColor(r, g, b, a); + + GLbitfield flags = GL_COLOR_BUFFER_BIT; + glClear(flags); + + return true; +} + +bool CRenderSystemGLES::IsExtSupported(const char* extension) const +{ + if (strcmp( extension, "GL_EXT_framebuffer_object" ) == 0) + { + // GLES has FBO as a core element, not an extension! + return true; + } + else + { + std::string name; + name = " "; + name += extension; + name += " "; + + return m_RenderExtensions.find(name) != std::string::npos; + } +} + +void CRenderSystemGLES::PresentRender(bool rendered, bool videoLayer) +{ + SetVSync(true); + + if (!m_bRenderCreated) + return; + + PresentRenderImpl(rendered); + + // if video is rendered to a separate layer, we should not block this thread + if (!rendered && !videoLayer) + KODI::TIME::Sleep(40ms); +} + +void CRenderSystemGLES::SetVSync(bool enable) +{ + if (m_bVsyncInit) + return; + + if (!m_bRenderCreated) + return; + + if (enable) + CLog::Log(LOGINFO, "GLES: Enabling VSYNC"); + else + CLog::Log(LOGINFO, "GLES: Disabling VSYNC"); + + m_bVsyncInit = true; + + SetVSyncImpl(enable); +} + +void CRenderSystemGLES::CaptureStateBlock() +{ + if (!m_bRenderCreated) + return; + + glMatrixProject.Push(); + glMatrixModview.Push(); + glMatrixTexture.Push(); + + glDisable(GL_SCISSOR_TEST); // fixes FBO corruption on Macs + glActiveTexture(GL_TEXTURE0); +//! @todo - NOTE: Only for Screensavers & Visualisations +// glColor3f(1.0, 1.0, 1.0); +} + +void CRenderSystemGLES::ApplyStateBlock() +{ + if (!m_bRenderCreated) + return; + + glMatrixProject.PopLoad(); + glMatrixModview.PopLoad(); + glMatrixTexture.PopLoad(); + glActiveTexture(GL_TEXTURE0); + glEnable(GL_BLEND); + glEnable(GL_SCISSOR_TEST); + glClear(GL_DEPTH_BUFFER_BIT); +} + +void CRenderSystemGLES::SetCameraPosition(const CPoint &camera, int screenWidth, int screenHeight, float stereoFactor) +{ + if (!m_bRenderCreated) + return; + + CPoint offset = camera - CPoint(screenWidth*0.5f, screenHeight*0.5f); + + float w = (float)m_viewPort[2]*0.5f; + float h = (float)m_viewPort[3]*0.5f; + + glMatrixModview->LoadIdentity(); + glMatrixModview->Translatef(-(w + offset.x - stereoFactor), +(h + offset.y), 0); + glMatrixModview->LookAt(0.0f, 0.0f, -2.0f * h, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f); + glMatrixModview.Load(); + + glMatrixProject->LoadIdentity(); + glMatrixProject->Frustum( (-w - offset.x)*0.5f, (w - offset.x)*0.5f, (-h + offset.y)*0.5f, (h + offset.y)*0.5f, h, 100*h); + glMatrixProject.Load(); +} + +void CRenderSystemGLES::Project(float &x, float &y, float &z) +{ + GLfloat coordX, coordY, coordZ; + if (CMatrixGL::Project(x, y, z, glMatrixModview.Get(), glMatrixProject.Get(), m_viewPort, &coordX, &coordY, &coordZ)) + { + x = coordX; + y = (float)(m_viewPort[1] + m_viewPort[3] - coordY); + z = 0; + } +} + +void CRenderSystemGLES::CalculateMaxTexturesize() +{ + // GLES cannot do PROXY textures to determine maximum size, + CLog::Log(LOGINFO, "GLES: Maximum texture width: {}", m_maxTextureSize); +} + +void CRenderSystemGLES::GetViewPort(CRect& viewPort) +{ + if (!m_bRenderCreated) + return; + + viewPort.x1 = m_viewPort[0]; + viewPort.y1 = m_height - m_viewPort[1] - m_viewPort[3]; + viewPort.x2 = m_viewPort[0] + m_viewPort[2]; + viewPort.y2 = viewPort.y1 + m_viewPort[3]; +} + +void CRenderSystemGLES::SetViewPort(const CRect& viewPort) +{ + if (!m_bRenderCreated) + return; + + glScissor((GLint) viewPort.x1, (GLint) (m_height - viewPort.y1 - viewPort.Height()), (GLsizei) viewPort.Width(), (GLsizei) viewPort.Height()); + glViewport((GLint) viewPort.x1, (GLint) (m_height - viewPort.y1 - viewPort.Height()), (GLsizei) viewPort.Width(), (GLsizei) viewPort.Height()); + m_viewPort[0] = viewPort.x1; + m_viewPort[1] = m_height - viewPort.y1 - viewPort.Height(); + m_viewPort[2] = viewPort.Width(); + m_viewPort[3] = viewPort.Height(); +} + +bool CRenderSystemGLES::ScissorsCanEffectClipping() +{ + if (m_pShader[m_method]) + return m_pShader[m_method]->HardwareClipIsPossible(); + + return false; +} + +CRect CRenderSystemGLES::ClipRectToScissorRect(const CRect &rect) +{ + if (!m_pShader[m_method]) + return CRect(); + float xFactor = m_pShader[m_method]->GetClipXFactor(); + float xOffset = m_pShader[m_method]->GetClipXOffset(); + float yFactor = m_pShader[m_method]->GetClipYFactor(); + float yOffset = m_pShader[m_method]->GetClipYOffset(); + return CRect(rect.x1 * xFactor + xOffset, + rect.y1 * yFactor + yOffset, + rect.x2 * xFactor + xOffset, + rect.y2 * yFactor + yOffset); +} + +void CRenderSystemGLES::SetScissors(const CRect &rect) +{ + if (!m_bRenderCreated) + return; + GLint x1 = MathUtils::round_int(static_cast<double>(rect.x1)); + GLint y1 = MathUtils::round_int(static_cast<double>(rect.y1)); + GLint x2 = MathUtils::round_int(static_cast<double>(rect.x2)); + GLint y2 = MathUtils::round_int(static_cast<double>(rect.y2)); + glScissor(x1, m_height - y2, x2-x1, y2-y1); +} + +void CRenderSystemGLES::ResetScissors() +{ + SetScissors(CRect(0, 0, (float)m_width, (float)m_height)); +} + +void CRenderSystemGLES::InitialiseShaders() +{ + std::string defines; + m_limitedColorRange = CServiceBroker::GetWinSystem()->UseLimitedColor(); + if (m_limitedColorRange) + { + defines += "#define KODI_LIMITED_RANGE 1\n"; + } + + m_pShader[ShaderMethodGLES::SM_DEFAULT] = + std::make_unique<CGLESShader>("gles_shader.vert", "gles_shader_default.frag", defines); + if (!m_pShader[ShaderMethodGLES::SM_DEFAULT]->CompileAndLink()) + { + m_pShader[ShaderMethodGLES::SM_DEFAULT]->Free(); + m_pShader[ShaderMethodGLES::SM_DEFAULT].reset(); + CLog::Log(LOGERROR, "GUI Shader gles_shader_default.frag - compile and link failed"); + } + + m_pShader[ShaderMethodGLES::SM_TEXTURE] = + std::make_unique<CGLESShader>("gles_shader_texture.frag", defines); + if (!m_pShader[ShaderMethodGLES::SM_TEXTURE]->CompileAndLink()) + { + m_pShader[ShaderMethodGLES::SM_TEXTURE]->Free(); + m_pShader[ShaderMethodGLES::SM_TEXTURE].reset(); + CLog::Log(LOGERROR, "GUI Shader gles_shader_texture.frag - compile and link failed"); + } + + m_pShader[ShaderMethodGLES::SM_MULTI] = + std::make_unique<CGLESShader>("gles_shader_multi.frag", defines); + if (!m_pShader[ShaderMethodGLES::SM_MULTI]->CompileAndLink()) + { + m_pShader[ShaderMethodGLES::SM_MULTI]->Free(); + m_pShader[ShaderMethodGLES::SM_MULTI].reset(); + CLog::Log(LOGERROR, "GUI Shader gles_shader_multi.frag - compile and link failed"); + } + + m_pShader[ShaderMethodGLES::SM_FONTS] = + std::make_unique<CGLESShader>("gles_shader_fonts.frag", defines); + if (!m_pShader[ShaderMethodGLES::SM_FONTS]->CompileAndLink()) + { + m_pShader[ShaderMethodGLES::SM_FONTS]->Free(); + m_pShader[ShaderMethodGLES::SM_FONTS].reset(); + CLog::Log(LOGERROR, "GUI Shader gles_shader_fonts.frag - compile and link failed"); + } + + m_pShader[ShaderMethodGLES::SM_TEXTURE_NOBLEND] = + std::make_unique<CGLESShader>("gles_shader_texture_noblend.frag", defines); + if (!m_pShader[ShaderMethodGLES::SM_TEXTURE_NOBLEND]->CompileAndLink()) + { + m_pShader[ShaderMethodGLES::SM_TEXTURE_NOBLEND]->Free(); + m_pShader[ShaderMethodGLES::SM_TEXTURE_NOBLEND].reset(); + CLog::Log(LOGERROR, "GUI Shader gles_shader_texture_noblend.frag - compile and link failed"); + } + + m_pShader[ShaderMethodGLES::SM_MULTI_BLENDCOLOR] = + std::make_unique<CGLESShader>("gles_shader_multi_blendcolor.frag", defines); + if (!m_pShader[ShaderMethodGLES::SM_MULTI_BLENDCOLOR]->CompileAndLink()) + { + m_pShader[ShaderMethodGLES::SM_MULTI_BLENDCOLOR]->Free(); + m_pShader[ShaderMethodGLES::SM_MULTI_BLENDCOLOR].reset(); + CLog::Log(LOGERROR, "GUI Shader gles_shader_multi_blendcolor.frag - compile and link failed"); + } + + m_pShader[ShaderMethodGLES::SM_TEXTURE_RGBA] = + std::make_unique<CGLESShader>("gles_shader_rgba.frag", defines); + if (!m_pShader[ShaderMethodGLES::SM_TEXTURE_RGBA]->CompileAndLink()) + { + m_pShader[ShaderMethodGLES::SM_TEXTURE_RGBA]->Free(); + m_pShader[ShaderMethodGLES::SM_TEXTURE_RGBA].reset(); + CLog::Log(LOGERROR, "GUI Shader gles_shader_rgba.frag - compile and link failed"); + } + + m_pShader[ShaderMethodGLES::SM_TEXTURE_RGBA_BLENDCOLOR] = + std::make_unique<CGLESShader>("gles_shader_rgba_blendcolor.frag", defines); + if (!m_pShader[ShaderMethodGLES::SM_TEXTURE_RGBA_BLENDCOLOR]->CompileAndLink()) + { + m_pShader[ShaderMethodGLES::SM_TEXTURE_RGBA_BLENDCOLOR]->Free(); + m_pShader[ShaderMethodGLES::SM_TEXTURE_RGBA_BLENDCOLOR].reset(); + CLog::Log(LOGERROR, "GUI Shader gles_shader_rgba_blendcolor.frag - compile and link failed"); + } + + m_pShader[ShaderMethodGLES::SM_TEXTURE_RGBA_BOB] = + std::make_unique<CGLESShader>("gles_shader_rgba_bob.frag", defines); + if (!m_pShader[ShaderMethodGLES::SM_TEXTURE_RGBA_BOB]->CompileAndLink()) + { + m_pShader[ShaderMethodGLES::SM_TEXTURE_RGBA_BOB]->Free(); + m_pShader[ShaderMethodGLES::SM_TEXTURE_RGBA_BOB].reset(); + CLog::Log(LOGERROR, "GUI Shader gles_shader_rgba_bob.frag - compile and link failed"); + } + + if (IsExtSupported("GL_OES_EGL_image_external")) + { + m_pShader[ShaderMethodGLES::SM_TEXTURE_RGBA_OES] = + std::make_unique<CGLESShader>("gles_shader_rgba_oes.frag", defines); + if (!m_pShader[ShaderMethodGLES::SM_TEXTURE_RGBA_OES]->CompileAndLink()) + { + m_pShader[ShaderMethodGLES::SM_TEXTURE_RGBA_OES]->Free(); + m_pShader[ShaderMethodGLES::SM_TEXTURE_RGBA_OES].reset(); + CLog::Log(LOGERROR, "GUI Shader gles_shader_rgba_oes.frag - compile and link failed"); + } + + + m_pShader[ShaderMethodGLES::SM_TEXTURE_RGBA_BOB_OES] = + std::make_unique<CGLESShader>("gles_shader_rgba_bob_oes.frag", defines); + if (!m_pShader[ShaderMethodGLES::SM_TEXTURE_RGBA_BOB_OES]->CompileAndLink()) + { + m_pShader[ShaderMethodGLES::SM_TEXTURE_RGBA_BOB_OES]->Free(); + m_pShader[ShaderMethodGLES::SM_TEXTURE_RGBA_BOB_OES].reset(); + CLog::Log(LOGERROR, "GUI Shader gles_shader_rgba_bob_oes.frag - compile and link failed"); + } + } + + m_pShader[ShaderMethodGLES::SM_TEXTURE_NOALPHA] = + std::make_unique<CGLESShader>("gles_shader_texture_noalpha.frag", defines); + if (!m_pShader[ShaderMethodGLES::SM_TEXTURE_NOALPHA]->CompileAndLink()) + { + m_pShader[ShaderMethodGLES::SM_TEXTURE_NOALPHA]->Free(); + m_pShader[ShaderMethodGLES::SM_TEXTURE_NOALPHA].reset(); + CLog::Log(LOGERROR, "GUI Shader gles_shader_texture_noalpha.frag - compile and link failed"); + } +} + +void CRenderSystemGLES::ReleaseShaders() +{ + if (m_pShader[ShaderMethodGLES::SM_DEFAULT]) + m_pShader[ShaderMethodGLES::SM_DEFAULT]->Free(); + m_pShader[ShaderMethodGLES::SM_DEFAULT].reset(); + + if (m_pShader[ShaderMethodGLES::SM_TEXTURE]) + m_pShader[ShaderMethodGLES::SM_TEXTURE]->Free(); + m_pShader[ShaderMethodGLES::SM_TEXTURE].reset(); + + if (m_pShader[ShaderMethodGLES::SM_MULTI]) + m_pShader[ShaderMethodGLES::SM_MULTI]->Free(); + m_pShader[ShaderMethodGLES::SM_MULTI].reset(); + + if (m_pShader[ShaderMethodGLES::SM_FONTS]) + m_pShader[ShaderMethodGLES::SM_FONTS]->Free(); + m_pShader[ShaderMethodGLES::SM_FONTS].reset(); + + if (m_pShader[ShaderMethodGLES::SM_TEXTURE_NOBLEND]) + m_pShader[ShaderMethodGLES::SM_TEXTURE_NOBLEND]->Free(); + m_pShader[ShaderMethodGLES::SM_TEXTURE_NOBLEND].reset(); + + if (m_pShader[ShaderMethodGLES::SM_MULTI_BLENDCOLOR]) + m_pShader[ShaderMethodGLES::SM_MULTI_BLENDCOLOR]->Free(); + m_pShader[ShaderMethodGLES::SM_MULTI_BLENDCOLOR].reset(); + + if (m_pShader[ShaderMethodGLES::SM_TEXTURE_RGBA]) + m_pShader[ShaderMethodGLES::SM_TEXTURE_RGBA]->Free(); + m_pShader[ShaderMethodGLES::SM_TEXTURE_RGBA].reset(); + + if (m_pShader[ShaderMethodGLES::SM_TEXTURE_RGBA_BLENDCOLOR]) + m_pShader[ShaderMethodGLES::SM_TEXTURE_RGBA_BLENDCOLOR]->Free(); + m_pShader[ShaderMethodGLES::SM_TEXTURE_RGBA_BLENDCOLOR].reset(); + + if (m_pShader[ShaderMethodGLES::SM_TEXTURE_RGBA_BOB]) + m_pShader[ShaderMethodGLES::SM_TEXTURE_RGBA_BOB]->Free(); + m_pShader[ShaderMethodGLES::SM_TEXTURE_RGBA_BOB].reset(); + + if (m_pShader[ShaderMethodGLES::SM_TEXTURE_RGBA_OES]) + m_pShader[ShaderMethodGLES::SM_TEXTURE_RGBA_OES]->Free(); + m_pShader[ShaderMethodGLES::SM_TEXTURE_RGBA_OES].reset(); + + if (m_pShader[ShaderMethodGLES::SM_TEXTURE_RGBA_BOB_OES]) + m_pShader[ShaderMethodGLES::SM_TEXTURE_RGBA_BOB_OES]->Free(); + m_pShader[ShaderMethodGLES::SM_TEXTURE_RGBA_BOB_OES].reset(); + + if (m_pShader[ShaderMethodGLES::SM_TEXTURE_NOALPHA]) + m_pShader[ShaderMethodGLES::SM_TEXTURE_NOALPHA]->Free(); + m_pShader[ShaderMethodGLES::SM_TEXTURE_NOALPHA].reset(); +} + +void CRenderSystemGLES::EnableGUIShader(ShaderMethodGLES method) +{ + m_method = method; + if (m_pShader[m_method]) + { + m_pShader[m_method]->Enable(); + } + else + { + CLog::Log(LOGERROR, "Invalid GUI Shader selected - {}", method); + } +} + +void CRenderSystemGLES::DisableGUIShader() +{ + if (m_pShader[m_method]) + { + m_pShader[m_method]->Disable(); + } + m_method = ShaderMethodGLES::SM_DEFAULT; +} + +GLint CRenderSystemGLES::GUIShaderGetPos() +{ + if (m_pShader[m_method]) + return m_pShader[m_method]->GetPosLoc(); + + return -1; +} + +GLint CRenderSystemGLES::GUIShaderGetCol() +{ + if (m_pShader[m_method]) + return m_pShader[m_method]->GetColLoc(); + + return -1; +} + +GLint CRenderSystemGLES::GUIShaderGetCoord0() +{ + if (m_pShader[m_method]) + return m_pShader[m_method]->GetCord0Loc(); + + return -1; +} + +GLint CRenderSystemGLES::GUIShaderGetCoord1() +{ + if (m_pShader[m_method]) + return m_pShader[m_method]->GetCord1Loc(); + + return -1; +} + +GLint CRenderSystemGLES::GUIShaderGetUniCol() +{ + if (m_pShader[m_method]) + return m_pShader[m_method]->GetUniColLoc(); + + return -1; +} + +GLint CRenderSystemGLES::GUIShaderGetCoord0Matrix() +{ + if (m_pShader[m_method]) + return m_pShader[m_method]->GetCoord0MatrixLoc(); + + return -1; +} + +GLint CRenderSystemGLES::GUIShaderGetField() +{ + if (m_pShader[m_method]) + return m_pShader[m_method]->GetFieldLoc(); + + return -1; +} + +GLint CRenderSystemGLES::GUIShaderGetStep() +{ + if (m_pShader[m_method]) + return m_pShader[m_method]->GetStepLoc(); + + return -1; +} + +GLint CRenderSystemGLES::GUIShaderGetContrast() +{ + if (m_pShader[m_method]) + return m_pShader[m_method]->GetContrastLoc(); + + return -1; +} + +GLint CRenderSystemGLES::GUIShaderGetBrightness() +{ + if (m_pShader[m_method]) + return m_pShader[m_method]->GetBrightnessLoc(); + + return -1; +} + +bool CRenderSystemGLES::SupportsStereo(RENDER_STEREO_MODE mode) const +{ + return CRenderSystemBase::SupportsStereo(mode); +} + +GLint CRenderSystemGLES::GUIShaderGetModel() +{ + if (m_pShader[m_method]) + return m_pShader[m_method]->GetModelLoc(); + + return -1; +} diff --git a/xbmc/rendering/gles/RenderSystemGLES.h b/xbmc/rendering/gles/RenderSystemGLES.h new file mode 100644 index 0000000..e0cd72b --- /dev/null +++ b/xbmc/rendering/gles/RenderSystemGLES.h @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2005-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 "GLESShader.h" +#include "rendering/RenderSystem.h" +#include "utils/ColorUtils.h" +#include "utils/Map.h" + +#include <map> + +#include <fmt/format.h> + +#include "system_gl.h" + +enum class ShaderMethodGLES +{ + SM_DEFAULT, + SM_TEXTURE, + SM_MULTI, + SM_FONTS, + SM_TEXTURE_NOBLEND, + SM_MULTI_BLENDCOLOR, + SM_TEXTURE_RGBA, + SM_TEXTURE_RGBA_OES, + SM_TEXTURE_RGBA_BLENDCOLOR, + SM_TEXTURE_RGBA_BOB, + SM_TEXTURE_RGBA_BOB_OES, + SM_TEXTURE_NOALPHA, + SM_MAX +}; + +template<> +struct fmt::formatter<ShaderMethodGLES> : fmt::formatter<std::string_view> +{ + template<typename FormatContext> + constexpr auto format(const ShaderMethodGLES& shaderMethod, FormatContext& ctx) + { + const auto it = ShaderMethodGLESMap.find(shaderMethod); + if (it == ShaderMethodGLESMap.cend()) + throw std::range_error("no string mapping found for shader method"); + + return fmt::formatter<string_view>::format(it->second, ctx); + } + +private: + static constexpr auto ShaderMethodGLESMap = make_map<ShaderMethodGLES, std::string_view>({ + {ShaderMethodGLES::SM_DEFAULT, "default"}, + {ShaderMethodGLES::SM_TEXTURE, "texture"}, + {ShaderMethodGLES::SM_MULTI, "multi"}, + {ShaderMethodGLES::SM_FONTS, "fonts"}, + {ShaderMethodGLES::SM_TEXTURE_NOBLEND, "texture no blending"}, + {ShaderMethodGLES::SM_MULTI_BLENDCOLOR, "multi blend colour"}, + {ShaderMethodGLES::SM_TEXTURE_RGBA, "texure rgba"}, + {ShaderMethodGLES::SM_TEXTURE_RGBA_OES, "texture rgba OES"}, + {ShaderMethodGLES::SM_TEXTURE_RGBA_BLENDCOLOR, "texture rgba blend colour"}, + {ShaderMethodGLES::SM_TEXTURE_RGBA_BOB, "texture rgba bob"}, + {ShaderMethodGLES::SM_TEXTURE_RGBA_BOB_OES, "texture rgba bob OES"}, + {ShaderMethodGLES::SM_TEXTURE_NOALPHA, "texture no alpha"}, + }); + + static_assert(static_cast<size_t>(ShaderMethodGLES::SM_MAX) == ShaderMethodGLESMap.size(), + "ShaderMethodGLESMap doesn't match the size of ShaderMethodGLES, did you forget to " + "add/remove a mapping?"); +}; + +class CRenderSystemGLES : public CRenderSystemBase +{ +public: + CRenderSystemGLES(); + ~CRenderSystemGLES() override = default; + + bool InitRenderSystem() override; + bool DestroyRenderSystem() override; + bool ResetRenderSystem(int width, int height) override; + + bool BeginRender() override; + bool EndRender() override; + void PresentRender(bool rendered, bool videoLayer) override; + bool ClearBuffers(UTILS::COLOR::Color color) override; + bool IsExtSupported(const char* extension) const override; + + void SetVSync(bool vsync); + void ResetVSync() { m_bVsyncInit = false; } + + void SetViewPort(const CRect& viewPort) override; + void GetViewPort(CRect& viewPort) override; + + bool ScissorsCanEffectClipping() override; + CRect ClipRectToScissorRect(const CRect &rect) override; + void SetScissors(const CRect& rect) override; + void ResetScissors() override; + + void CaptureStateBlock() override; + void ApplyStateBlock() override; + + void SetCameraPosition(const CPoint &camera, int screenWidth, int screenHeight, float stereoFactor = 0.0f) override; + + bool SupportsStereo(RENDER_STEREO_MODE mode) const override; + + void Project(float &x, float &y, float &z) override; + + std::string GetShaderPath(const std::string &filename) override { return "GLES/2.0/"; } + + void InitialiseShaders(); + void ReleaseShaders(); + void EnableGUIShader(ShaderMethodGLES method); + void DisableGUIShader(); + + GLint GUIShaderGetPos(); + GLint GUIShaderGetCol(); + GLint GUIShaderGetCoord0(); + GLint GUIShaderGetCoord1(); + GLint GUIShaderGetUniCol(); + GLint GUIShaderGetCoord0Matrix(); + GLint GUIShaderGetField(); + GLint GUIShaderGetStep(); + GLint GUIShaderGetContrast(); + GLint GUIShaderGetBrightness(); + GLint GUIShaderGetModel(); + +protected: + virtual void SetVSyncImpl(bool enable) = 0; + virtual void PresentRenderImpl(bool rendered) = 0; + void CalculateMaxTexturesize(); + + bool m_bVsyncInit{false}; + int m_width; + int m_height; + + std::string m_RenderExtensions; + + std::map<ShaderMethodGLES, std::unique_ptr<CGLESShader>> m_pShader; + ShaderMethodGLES m_method = ShaderMethodGLES::SM_DEFAULT; + + GLint m_viewPort[4]; +}; diff --git a/xbmc/rendering/gles/ScreenshotSurfaceGLES.cpp b/xbmc/rendering/gles/ScreenshotSurfaceGLES.cpp new file mode 100644 index 0000000..3c290ec --- /dev/null +++ b/xbmc/rendering/gles/ScreenshotSurfaceGLES.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2005-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 "ScreenshotSurfaceGLES.h" + +#include "ServiceBroker.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "utils/Screenshot.h" +#include "windowing/GraphicContext.h" + +#include <mutex> +#include <vector> + +#include "system_gl.h" + +void CScreenshotSurfaceGLES::Register() +{ + CScreenShot::Register(CScreenshotSurfaceGLES::CreateSurface); +} + +std::unique_ptr<IScreenshotSurface> CScreenshotSurfaceGLES::CreateSurface() +{ + return std::unique_ptr<CScreenshotSurfaceGLES>(new CScreenshotSurfaceGLES()); +} + +bool CScreenshotSurfaceGLES::Capture() +{ + CWinSystemBase* winsystem = CServiceBroker::GetWinSystem(); + if (!winsystem) + return false; + + CGUIComponent* gui = CServiceBroker::GetGUI(); + if (!gui) + return false; + + std::unique_lock<CCriticalSection> lock(winsystem->GetGfxContext()); + gui->GetWindowManager().Render(); + + //get current viewport + GLint viewport[4]; + glGetIntegerv(GL_VIEWPORT, viewport); + + m_width = viewport[2] - viewport[0]; + m_height = viewport[3] - viewport[1]; + m_stride = m_width * 4; + std::vector<uint8_t> surface(m_stride * m_height); + + //read pixels from the backbuffer + glReadPixels(viewport[0], viewport[1], viewport[2], viewport[3], GL_RGBA, GL_UNSIGNED_BYTE, static_cast<GLvoid*>(surface.data())); + + //make a new buffer and copy the read image to it with the Y axis inverted + m_buffer = new unsigned char[m_stride * m_height]; + for (int y = 0; y < m_height; y++) + { + // we need to save in BGRA order so XOR Swap RGBA -> BGRA + unsigned char* swap_pixels = surface.data() + (m_height - y - 1) * m_stride; + for (int x = 0; x < m_width; x++, swap_pixels += 4) + { + std::swap(swap_pixels[0], swap_pixels[2]); + } + + memcpy(m_buffer + y * m_stride, surface.data() + (m_height - y - 1) * m_stride, m_stride); + } + + return m_buffer != nullptr; +} diff --git a/xbmc/rendering/gles/ScreenshotSurfaceGLES.h b/xbmc/rendering/gles/ScreenshotSurfaceGLES.h new file mode 100644 index 0000000..1ca1730 --- /dev/null +++ b/xbmc/rendering/gles/ScreenshotSurfaceGLES.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2005-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 "utils/IScreenshotSurface.h" + +#include <memory> + +class CScreenshotSurfaceGLES : public IScreenshotSurface +{ +public: + static void Register(); + static std::unique_ptr<IScreenshotSurface> CreateSurface(); + + bool Capture() override; +}; |