Clean up
This commit is contained in:
341
src/render/InputFrameTexture.cpp
Normal file
341
src/render/InputFrameTexture.cpp
Normal file
@@ -0,0 +1,341 @@
|
||||
#include "InputFrameTexture.h"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
#ifndef GL_FRAMEBUFFER_BINDING
|
||||
#define GL_FRAMEBUFFER_BINDING 0x8CA6
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr GLuint kUyvyTextureUnit = 0;
|
||||
|
||||
const char* kDecodeVertexShader = R"GLSL(
|
||||
#version 430 core
|
||||
out vec2 vTexCoord;
|
||||
void main()
|
||||
{
|
||||
vec2 positions[3] = vec2[3](
|
||||
vec2(-1.0, -1.0),
|
||||
vec2( 3.0, -1.0),
|
||||
vec2(-1.0, 3.0));
|
||||
vec2 texCoords[3] = vec2[3](
|
||||
vec2(0.0, 0.0),
|
||||
vec2(2.0, 0.0),
|
||||
vec2(0.0, 2.0));
|
||||
gl_Position = vec4(positions[gl_VertexID], 0.0, 1.0);
|
||||
vTexCoord = texCoords[gl_VertexID];
|
||||
}
|
||||
)GLSL";
|
||||
|
||||
const char* kUyvyDecodeFragmentShader = R"GLSL(
|
||||
#version 430 core
|
||||
layout(binding = 0) uniform sampler2D uPackedUyvy;
|
||||
uniform vec2 uDecodedSize;
|
||||
in vec2 vTexCoord;
|
||||
out vec4 fragColor;
|
||||
|
||||
vec4 rec709YCbCr2rgba(float yByte, float cbByte, float crByte)
|
||||
{
|
||||
float y = (yByte - 16.0) / 219.0;
|
||||
float cb = (cbByte - 16.0) / 224.0 - 0.5;
|
||||
float cr = (crByte - 16.0) / 224.0 - 0.5;
|
||||
return vec4(
|
||||
y + 1.5748 * cr,
|
||||
y - 0.1873 * cb - 0.4681 * cr,
|
||||
y + 1.8556 * cb,
|
||||
1.0);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
ivec2 decodedSize = ivec2(uDecodedSize);
|
||||
ivec2 outputCoord = ivec2(clamp(gl_FragCoord.xy, vec2(0.0), vec2(decodedSize - ivec2(1))));
|
||||
int sourceY = decodedSize.y - 1 - outputCoord.y;
|
||||
ivec2 packedCoord = ivec2(clamp(outputCoord.x / 2, 0, max(decodedSize.x / 2 - 1, 0)), sourceY);
|
||||
vec4 macroPixel = texelFetch(uPackedUyvy, packedCoord, 0) * 255.0;
|
||||
float ySample = (outputCoord.x & 1) != 0 ? macroPixel.a : macroPixel.g;
|
||||
fragColor = clamp(rec709YCbCr2rgba(ySample, macroPixel.r, macroPixel.b), vec4(0.0), vec4(1.0));
|
||||
}
|
||||
)GLSL";
|
||||
}
|
||||
|
||||
InputFrameTexture::~InputFrameTexture()
|
||||
{
|
||||
ShutdownGl();
|
||||
}
|
||||
|
||||
GLuint InputFrameTexture::PollAndUpload(InputFrameMailbox* mailbox)
|
||||
{
|
||||
if (mailbox == nullptr)
|
||||
return mTexture;
|
||||
|
||||
InputFrame frame;
|
||||
if (!mailbox->TryAcquireOldest(frame))
|
||||
{
|
||||
++mUploadMisses;
|
||||
mLastUploadMilliseconds = 0.0;
|
||||
return mTexture;
|
||||
}
|
||||
|
||||
if (frame.bytes != nullptr && frame.pixelFormat == VideoIOPixelFormat::Bgra8 && EnsureTexture(frame))
|
||||
{
|
||||
mLastFrameFormatSupported = true;
|
||||
const auto uploadStart = std::chrono::steady_clock::now();
|
||||
UploadBgra8FrameFlippedVertically(frame);
|
||||
const auto uploadEnd = std::chrono::steady_clock::now();
|
||||
mLastUploadMilliseconds = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(uploadEnd - uploadStart).count();
|
||||
++mUploadedFrames;
|
||||
}
|
||||
else if (frame.bytes != nullptr && frame.pixelFormat == VideoIOPixelFormat::Uyvy8 && EnsureTexture(frame) && EnsureRawUyvyTexture(frame) && EnsureDecodeProgram())
|
||||
{
|
||||
mLastFrameFormatSupported = true;
|
||||
const auto uploadStart = std::chrono::steady_clock::now();
|
||||
UploadUyvy8Frame(frame);
|
||||
DecodeUyvy8Frame(frame);
|
||||
const auto uploadEnd = std::chrono::steady_clock::now();
|
||||
mLastUploadMilliseconds = std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(uploadEnd - uploadStart).count();
|
||||
++mUploadedFrames;
|
||||
}
|
||||
else
|
||||
{
|
||||
mLastFrameFormatSupported = frame.pixelFormat == VideoIOPixelFormat::Bgra8 || frame.pixelFormat == VideoIOPixelFormat::Uyvy8;
|
||||
mLastUploadMilliseconds = 0.0;
|
||||
}
|
||||
|
||||
mailbox->Release(frame);
|
||||
return mTexture;
|
||||
}
|
||||
|
||||
void InputFrameTexture::ShutdownGl()
|
||||
{
|
||||
if (mTexture != 0)
|
||||
glDeleteTextures(1, &mTexture);
|
||||
if (mRawTexture != 0)
|
||||
glDeleteTextures(1, &mRawTexture);
|
||||
mTexture = 0;
|
||||
mRawTexture = 0;
|
||||
mWidth = 0;
|
||||
mHeight = 0;
|
||||
mRawWidth = 0;
|
||||
mRawHeight = 0;
|
||||
DestroyDecodeResources();
|
||||
}
|
||||
|
||||
bool InputFrameTexture::EnsureTexture(const InputFrame& frame)
|
||||
{
|
||||
if (frame.width == 0 || frame.height == 0)
|
||||
return false;
|
||||
|
||||
if (mTexture != 0 && mWidth == frame.width && mHeight == frame.height)
|
||||
return true;
|
||||
|
||||
ShutdownGl();
|
||||
glGenTextures(1, &mTexture);
|
||||
glBindTexture(GL_TEXTURE_2D, mTexture);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexImage2D(
|
||||
GL_TEXTURE_2D,
|
||||
0,
|
||||
GL_RGBA8,
|
||||
static_cast<GLsizei>(frame.width),
|
||||
static_cast<GLsizei>(frame.height),
|
||||
0,
|
||||
GL_BGRA,
|
||||
GL_UNSIGNED_INT_8_8_8_8_REV,
|
||||
nullptr);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
mWidth = frame.width;
|
||||
mHeight = frame.height;
|
||||
return mTexture != 0;
|
||||
}
|
||||
|
||||
bool InputFrameTexture::EnsureRawUyvyTexture(const InputFrame& frame)
|
||||
{
|
||||
if (frame.width == 0 || frame.height == 0)
|
||||
return false;
|
||||
|
||||
const unsigned rawWidth = (frame.width + 1u) / 2u;
|
||||
if (mRawTexture != 0 && mRawWidth == rawWidth && mRawHeight == frame.height)
|
||||
return true;
|
||||
|
||||
if (mRawTexture != 0)
|
||||
glDeleteTextures(1, &mRawTexture);
|
||||
mRawTexture = 0;
|
||||
glGenTextures(1, &mRawTexture);
|
||||
glBindTexture(GL_TEXTURE_2D, mRawTexture);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexImage2D(
|
||||
GL_TEXTURE_2D,
|
||||
0,
|
||||
GL_RGBA8,
|
||||
static_cast<GLsizei>(rawWidth),
|
||||
static_cast<GLsizei>(frame.height),
|
||||
0,
|
||||
GL_RGBA,
|
||||
GL_UNSIGNED_BYTE,
|
||||
nullptr);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
|
||||
mRawWidth = rawWidth;
|
||||
mRawHeight = frame.height;
|
||||
return mRawTexture != 0;
|
||||
}
|
||||
|
||||
void InputFrameTexture::UploadBgra8FrameFlippedVertically(const InputFrame& frame)
|
||||
{
|
||||
glBindTexture(GL_TEXTURE_2D, mTexture);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, frame.rowBytes > 0 ? static_cast<GLint>(frame.rowBytes / 4) : 0);
|
||||
|
||||
const unsigned char* sourceBytes = static_cast<const unsigned char*>(frame.bytes);
|
||||
for (unsigned destinationY = 0; destinationY < frame.height; ++destinationY)
|
||||
{
|
||||
const unsigned sourceY = frame.height - 1u - destinationY;
|
||||
const unsigned char* sourceRow = sourceBytes + static_cast<std::size_t>(sourceY) * static_cast<std::size_t>(frame.rowBytes);
|
||||
glTexSubImage2D(
|
||||
GL_TEXTURE_2D,
|
||||
0,
|
||||
0,
|
||||
static_cast<GLint>(destinationY),
|
||||
static_cast<GLsizei>(frame.width),
|
||||
1,
|
||||
GL_BGRA,
|
||||
GL_UNSIGNED_INT_8_8_8_8_REV,
|
||||
sourceRow);
|
||||
}
|
||||
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
void InputFrameTexture::UploadUyvy8Frame(const InputFrame& frame)
|
||||
{
|
||||
glBindTexture(GL_TEXTURE_2D, mRawTexture);
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, frame.rowBytes > 0 ? static_cast<GLint>(frame.rowBytes / 4) : 0);
|
||||
glTexSubImage2D(
|
||||
GL_TEXTURE_2D,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
static_cast<GLsizei>((frame.width + 1u) / 2u),
|
||||
static_cast<GLsizei>(frame.height),
|
||||
GL_RGBA,
|
||||
GL_UNSIGNED_BYTE,
|
||||
frame.bytes);
|
||||
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
void InputFrameTexture::DecodeUyvy8Frame(const InputFrame& frame)
|
||||
{
|
||||
GLint previousFramebuffer = 0;
|
||||
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &previousFramebuffer);
|
||||
|
||||
if (mDecodeFramebuffer == 0)
|
||||
glGenFramebuffers(1, &mDecodeFramebuffer);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, mDecodeFramebuffer);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTexture, 0);
|
||||
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
||||
{
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, static_cast<GLuint>(previousFramebuffer));
|
||||
return;
|
||||
}
|
||||
|
||||
glViewport(0, 0, static_cast<GLsizei>(frame.width), static_cast<GLsizei>(frame.height));
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glDisable(GL_BLEND);
|
||||
glUseProgram(mDecodeProgram);
|
||||
const GLint decodedSizeLocation = glGetUniformLocation(mDecodeProgram, "uDecodedSize");
|
||||
if (decodedSizeLocation >= 0)
|
||||
glUniform2f(decodedSizeLocation, static_cast<GLfloat>(frame.width), static_cast<GLfloat>(frame.height));
|
||||
glActiveTexture(GL_TEXTURE0 + kUyvyTextureUnit);
|
||||
glBindTexture(GL_TEXTURE_2D, mRawTexture);
|
||||
glBindVertexArray(mDecodeVertexArray);
|
||||
glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||
glBindVertexArray(0);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glUseProgram(0);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, static_cast<GLuint>(previousFramebuffer));
|
||||
}
|
||||
|
||||
bool InputFrameTexture::EnsureDecodeProgram()
|
||||
{
|
||||
if (mDecodeProgram != 0)
|
||||
return true;
|
||||
|
||||
if (!CompileShader(GL_VERTEX_SHADER, kDecodeVertexShader, mDecodeVertexShader))
|
||||
return false;
|
||||
if (!CompileShader(GL_FRAGMENT_SHADER, kUyvyDecodeFragmentShader, mDecodeFragmentShader))
|
||||
return false;
|
||||
if (!LinkProgram(mDecodeVertexShader, mDecodeFragmentShader, mDecodeProgram))
|
||||
return false;
|
||||
|
||||
glUseProgram(mDecodeProgram);
|
||||
const GLint samplerLocation = glGetUniformLocation(mDecodeProgram, "uPackedUyvy");
|
||||
if (samplerLocation >= 0)
|
||||
glUniform1i(samplerLocation, static_cast<GLint>(kUyvyTextureUnit));
|
||||
glUseProgram(0);
|
||||
|
||||
if (mDecodeVertexArray == 0)
|
||||
glGenVertexArrays(1, &mDecodeVertexArray);
|
||||
return mDecodeProgram != 0 && mDecodeVertexArray != 0;
|
||||
}
|
||||
|
||||
void InputFrameTexture::DestroyDecodeResources()
|
||||
{
|
||||
if (mDecodeFramebuffer != 0)
|
||||
glDeleteFramebuffers(1, &mDecodeFramebuffer);
|
||||
if (mDecodeVertexArray != 0)
|
||||
glDeleteVertexArrays(1, &mDecodeVertexArray);
|
||||
if (mDecodeProgram != 0)
|
||||
glDeleteProgram(mDecodeProgram);
|
||||
if (mDecodeVertexShader != 0)
|
||||
glDeleteShader(mDecodeVertexShader);
|
||||
if (mDecodeFragmentShader != 0)
|
||||
glDeleteShader(mDecodeFragmentShader);
|
||||
mDecodeFramebuffer = 0;
|
||||
mDecodeVertexArray = 0;
|
||||
mDecodeProgram = 0;
|
||||
mDecodeVertexShader = 0;
|
||||
mDecodeFragmentShader = 0;
|
||||
}
|
||||
|
||||
bool InputFrameTexture::CompileShader(GLenum shaderType, const char* source, GLuint& shader)
|
||||
{
|
||||
shader = glCreateShader(shaderType);
|
||||
glShaderSource(shader, 1, &source, nullptr);
|
||||
glCompileShader(shader);
|
||||
GLint compileResult = GL_FALSE;
|
||||
glGetShaderiv(shader, GL_COMPILE_STATUS, &compileResult);
|
||||
if (compileResult != GL_FALSE)
|
||||
return true;
|
||||
glDeleteShader(shader);
|
||||
shader = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool InputFrameTexture::LinkProgram(GLuint vertexShader, GLuint fragmentShader, GLuint& program)
|
||||
{
|
||||
program = glCreateProgram();
|
||||
glAttachShader(program, vertexShader);
|
||||
glAttachShader(program, fragmentShader);
|
||||
glLinkProgram(program);
|
||||
GLint linkResult = GL_FALSE;
|
||||
glGetProgramiv(program, GL_LINK_STATUS, &linkResult);
|
||||
if (linkResult != GL_FALSE)
|
||||
return true;
|
||||
glDeleteProgram(program);
|
||||
program = 0;
|
||||
return false;
|
||||
}
|
||||
50
src/render/InputFrameTexture.h
Normal file
50
src/render/InputFrameTexture.h
Normal file
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include "../frames/InputFrameMailbox.h"
|
||||
#include "GLExtensions.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
class InputFrameTexture
|
||||
{
|
||||
public:
|
||||
InputFrameTexture() = default;
|
||||
InputFrameTexture(const InputFrameTexture&) = delete;
|
||||
InputFrameTexture& operator=(const InputFrameTexture&) = delete;
|
||||
~InputFrameTexture();
|
||||
|
||||
GLuint PollAndUpload(InputFrameMailbox* mailbox);
|
||||
GLuint Texture() const { return mTexture; }
|
||||
uint64_t UploadedFrames() const { return mUploadedFrames; }
|
||||
uint64_t UploadMisses() const { return mUploadMisses; }
|
||||
double LastUploadMilliseconds() const { return mLastUploadMilliseconds; }
|
||||
bool LastFrameFormatSupported() const { return mLastFrameFormatSupported; }
|
||||
void ShutdownGl();
|
||||
|
||||
private:
|
||||
bool EnsureTexture(const InputFrame& frame);
|
||||
bool EnsureRawUyvyTexture(const InputFrame& frame);
|
||||
bool EnsureDecodeProgram();
|
||||
void UploadBgra8FrameFlippedVertically(const InputFrame& frame);
|
||||
void UploadUyvy8Frame(const InputFrame& frame);
|
||||
void DecodeUyvy8Frame(const InputFrame& frame);
|
||||
void DestroyDecodeResources();
|
||||
static bool CompileShader(GLenum shaderType, const char* source, GLuint& shader);
|
||||
static bool LinkProgram(GLuint vertexShader, GLuint fragmentShader, GLuint& program);
|
||||
|
||||
GLuint mTexture = 0;
|
||||
GLuint mRawTexture = 0;
|
||||
GLuint mDecodeFramebuffer = 0;
|
||||
GLuint mDecodeVertexArray = 0;
|
||||
GLuint mDecodeProgram = 0;
|
||||
GLuint mDecodeVertexShader = 0;
|
||||
GLuint mDecodeFragmentShader = 0;
|
||||
unsigned mWidth = 0;
|
||||
unsigned mHeight = 0;
|
||||
unsigned mRawWidth = 0;
|
||||
unsigned mRawHeight = 0;
|
||||
uint64_t mUploadedFrames = 0;
|
||||
uint64_t mUploadMisses = 0;
|
||||
double mLastUploadMilliseconds = 0.0;
|
||||
bool mLastFrameFormatSupported = true;
|
||||
};
|
||||
49
src/render/RenderCadenceClock.cpp
Normal file
49
src/render/RenderCadenceClock.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#include "RenderCadenceClock.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
RenderCadenceClock::RenderCadenceClock(double frameDurationMilliseconds)
|
||||
{
|
||||
mFrameDuration = std::chrono::duration_cast<Duration>(std::chrono::duration<double, std::milli>(frameDurationMilliseconds));
|
||||
if (mFrameDuration <= Duration::zero())
|
||||
mFrameDuration = std::chrono::milliseconds(16);
|
||||
Reset();
|
||||
}
|
||||
|
||||
void RenderCadenceClock::Reset(TimePoint now)
|
||||
{
|
||||
mNextRenderTime = now;
|
||||
mPendingFrameAdvance = 1;
|
||||
mOverrunCount = 0;
|
||||
mSkippedFrameCount = 0;
|
||||
}
|
||||
|
||||
RenderCadenceClock::Tick RenderCadenceClock::Poll(TimePoint now)
|
||||
{
|
||||
Tick tick;
|
||||
if (now < mNextRenderTime)
|
||||
{
|
||||
tick.sleepFor = std::min(Duration(std::chrono::milliseconds(1)), mNextRenderTime - now);
|
||||
return tick;
|
||||
}
|
||||
|
||||
tick.due = true;
|
||||
mPendingFrameAdvance = 1;
|
||||
const Duration lateBy = now - mNextRenderTime;
|
||||
if (lateBy > mFrameDuration)
|
||||
{
|
||||
tick.skippedFrames = static_cast<uint64_t>(lateBy / mFrameDuration);
|
||||
mPendingFrameAdvance += tick.skippedFrames;
|
||||
++mOverrunCount;
|
||||
mSkippedFrameCount += tick.skippedFrames;
|
||||
}
|
||||
return tick;
|
||||
}
|
||||
|
||||
void RenderCadenceClock::MarkRendered(TimePoint now)
|
||||
{
|
||||
mNextRenderTime += mFrameDuration * mPendingFrameAdvance;
|
||||
mPendingFrameAdvance = 1;
|
||||
if (now - mNextRenderTime > mFrameDuration * 4)
|
||||
mNextRenderTime = now + mFrameDuration;
|
||||
}
|
||||
37
src/render/RenderCadenceClock.h
Normal file
37
src/render/RenderCadenceClock.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
|
||||
class RenderCadenceClock
|
||||
{
|
||||
public:
|
||||
using Clock = std::chrono::steady_clock;
|
||||
using Duration = Clock::duration;
|
||||
using TimePoint = Clock::time_point;
|
||||
|
||||
struct Tick
|
||||
{
|
||||
bool due = false;
|
||||
uint64_t skippedFrames = 0;
|
||||
Duration sleepFor = Duration::zero();
|
||||
};
|
||||
|
||||
explicit RenderCadenceClock(double frameDurationMilliseconds = 1000.0 / 60.0);
|
||||
|
||||
void Reset(TimePoint now = Clock::now());
|
||||
Tick Poll(TimePoint now = Clock::now());
|
||||
void MarkRendered(TimePoint now = Clock::now());
|
||||
|
||||
Duration FrameDuration() const { return mFrameDuration; }
|
||||
TimePoint NextRenderTime() const { return mNextRenderTime; }
|
||||
uint64_t OverrunCount() const { return mOverrunCount; }
|
||||
uint64_t SkippedFrameCount() const { return mSkippedFrameCount; }
|
||||
|
||||
private:
|
||||
Duration mFrameDuration;
|
||||
TimePoint mNextRenderTime = Clock::now();
|
||||
uint64_t mPendingFrameAdvance = 1;
|
||||
uint64_t mOverrunCount = 0;
|
||||
uint64_t mSkippedFrameCount = 0;
|
||||
};
|
||||
389
src/render/RenderThread.cpp
Normal file
389
src/render/RenderThread.cpp
Normal file
@@ -0,0 +1,389 @@
|
||||
#include "RenderThread.h"
|
||||
|
||||
#include "../frames/InputFrameMailbox.h"
|
||||
#include "../frames/SystemFrameExchange.h"
|
||||
#include "../frames/SystemFrameTypes.h"
|
||||
#include "../logging/Logger.h"
|
||||
#include "../platform/HiddenGlWindow.h"
|
||||
#include "InputFrameTexture.h"
|
||||
#include "readback/Bgra8ReadbackPipeline.h"
|
||||
#include "GLExtensions.h"
|
||||
#include "runtime/RuntimeRenderScene.h"
|
||||
#include "runtime/RuntimeShaderRenderer.h"
|
||||
#include "SimpleMotionRenderer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
RenderThread::RenderThread(SystemFrameExchange& frameExchange, Config config) :
|
||||
mFrameExchange(frameExchange),
|
||||
mConfig(config)
|
||||
{
|
||||
}
|
||||
|
||||
RenderThread::RenderThread(SystemFrameExchange& frameExchange, InputFrameMailbox* inputMailbox, Config config) :
|
||||
mFrameExchange(frameExchange),
|
||||
mInputMailbox(inputMailbox),
|
||||
mConfig(config)
|
||||
{
|
||||
}
|
||||
|
||||
RenderThread::~RenderThread()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
bool RenderThread::Start(std::string& error)
|
||||
{
|
||||
if (mThread.joinable())
|
||||
return true;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mStartupMutex);
|
||||
mStarted = false;
|
||||
mStartupError.clear();
|
||||
}
|
||||
|
||||
mStopping.store(false, std::memory_order_release);
|
||||
mThread = std::thread([this]() { ThreadMain(); });
|
||||
|
||||
std::unique_lock<std::mutex> lock(mStartupMutex);
|
||||
if (!mStartupCondition.wait_for(lock, std::chrono::seconds(3), [this]() {
|
||||
return mStarted || !mStartupError.empty();
|
||||
}))
|
||||
{
|
||||
error = "Timed out starting render thread.";
|
||||
return false;
|
||||
}
|
||||
if (!mStartupError.empty())
|
||||
{
|
||||
error = mStartupError;
|
||||
lock.unlock();
|
||||
if (mThread.joinable())
|
||||
mThread.join();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void RenderThread::Stop()
|
||||
{
|
||||
mStopping.store(true, std::memory_order_release);
|
||||
if (mThread.joinable())
|
||||
mThread.join();
|
||||
}
|
||||
|
||||
RenderThread::Metrics RenderThread::GetMetrics() const
|
||||
{
|
||||
Metrics metrics;
|
||||
metrics.renderedFrames = mRenderedFrames.load(std::memory_order_relaxed);
|
||||
metrics.completedReadbacks = mCompletedReadbacks.load(std::memory_order_relaxed);
|
||||
metrics.acquireMisses = mAcquireMisses.load(std::memory_order_relaxed);
|
||||
metrics.pboQueueMisses = mPboQueueMisses.load(std::memory_order_relaxed);
|
||||
metrics.clockOverruns = mClockOverruns.load(std::memory_order_relaxed);
|
||||
metrics.skippedFrames = mSkippedFrames.load(std::memory_order_relaxed);
|
||||
metrics.shaderBuildsCommitted = mShaderBuildsCommitted.load(std::memory_order_relaxed);
|
||||
metrics.shaderBuildFailures = mShaderBuildFailures.load(std::memory_order_relaxed);
|
||||
metrics.renderFrameMilliseconds = mRenderFrameMilliseconds.load(std::memory_order_relaxed);
|
||||
metrics.renderFrameBudgetUsedPercent = mRenderFrameBudgetUsedPercent.load(std::memory_order_relaxed);
|
||||
metrics.renderFrameMaxMilliseconds = mRenderFrameMaxMilliseconds.load(std::memory_order_relaxed);
|
||||
metrics.readbackQueueMilliseconds = mReadbackQueueMilliseconds.load(std::memory_order_relaxed);
|
||||
metrics.completedReadbackCopyMilliseconds = mCompletedReadbackCopyMilliseconds.load(std::memory_order_relaxed);
|
||||
metrics.inputFramesReceived = mInputFramesReceived.load(std::memory_order_relaxed);
|
||||
metrics.inputFramesDropped = mInputFramesDropped.load(std::memory_order_relaxed);
|
||||
metrics.inputConsumeMisses = mInputConsumeMisses.load(std::memory_order_relaxed);
|
||||
metrics.inputUploadMisses = mInputUploadMisses.load(std::memory_order_relaxed);
|
||||
metrics.inputReadyFrames = mInputReadyFrames.load(std::memory_order_relaxed);
|
||||
metrics.inputReadingFrames = mInputReadingFrames.load(std::memory_order_relaxed);
|
||||
metrics.inputLatestAgeMilliseconds = mInputLatestAgeMilliseconds.load(std::memory_order_relaxed);
|
||||
metrics.inputUploadMilliseconds = mInputUploadMilliseconds.load(std::memory_order_relaxed);
|
||||
metrics.inputFormatSupported = mInputFormatSupported.load(std::memory_order_relaxed);
|
||||
metrics.inputSignalPresent = mInputSignalPresent.load(std::memory_order_relaxed);
|
||||
return metrics;
|
||||
}
|
||||
|
||||
void RenderThread::ThreadMain()
|
||||
{
|
||||
RenderCadenceCompositor::TryLog(RenderCadenceCompositor::LogLevel::Log, "render-thread", "Render thread starting.");
|
||||
HiddenGlWindow window;
|
||||
std::string error;
|
||||
if (!window.Create(mConfig.width, mConfig.height, error))
|
||||
{
|
||||
SignalStartupFailure(error.empty() ? "OpenGL context creation failed." : error);
|
||||
return;
|
||||
}
|
||||
std::unique_ptr<HiddenGlWindow> prepareWindow = std::make_unique<HiddenGlWindow>();
|
||||
if (!prepareWindow->CreateShared(mConfig.width, mConfig.height, window.DeviceContext(), window.Context(), error))
|
||||
{
|
||||
SignalStartupFailure(error.empty() ? "Runtime shader prepare shared context creation failed." : error);
|
||||
return;
|
||||
}
|
||||
if (!window.MakeCurrent())
|
||||
{
|
||||
SignalStartupFailure("OpenGL context creation failed.");
|
||||
return;
|
||||
}
|
||||
if (!ResolveGLExtensions())
|
||||
{
|
||||
SignalStartupFailure("OpenGL extension resolution failed.");
|
||||
return;
|
||||
}
|
||||
|
||||
SimpleMotionRenderer renderer;
|
||||
RuntimeRenderScene runtimeRenderScene;
|
||||
Bgra8ReadbackPipeline readback;
|
||||
InputFrameTexture inputTexture;
|
||||
if (!runtimeRenderScene.StartPrepareWorker(std::move(prepareWindow), error))
|
||||
{
|
||||
SignalStartupFailure(error.empty() ? "Runtime shader prepare worker initialization failed." : error);
|
||||
return;
|
||||
}
|
||||
if (!renderer.InitializeGl(mConfig.width, mConfig.height) || !readback.Initialize(mConfig.width, mConfig.height, mConfig.pboDepth))
|
||||
{
|
||||
SignalStartupFailure("Render pipeline initialization failed.");
|
||||
return;
|
||||
}
|
||||
|
||||
RenderCadenceClock clock(mConfig.frameDurationMilliseconds);
|
||||
uint64_t frameIndex = 0;
|
||||
mRunning.store(true, std::memory_order_release);
|
||||
SignalStarted();
|
||||
|
||||
while (!mStopping.load(std::memory_order_acquire))
|
||||
{
|
||||
readback.ConsumeCompleted(
|
||||
[this](SystemFrame& frame) { return mFrameExchange.AcquireForRender(frame); },
|
||||
[this](const SystemFrame& frame) { return mFrameExchange.PublishCompleted(frame); },
|
||||
[this]() {
|
||||
CountAcquireMiss();
|
||||
},
|
||||
[this]() { CountCompleted(); });
|
||||
PublishReadbackMetrics(readback);
|
||||
|
||||
const auto now = RenderCadenceClock::Clock::now();
|
||||
const RenderCadenceClock::Tick tick = clock.Poll(now);
|
||||
if (!tick.due)
|
||||
{
|
||||
if (tick.sleepFor > RenderCadenceClock::Duration::zero())
|
||||
std::this_thread::sleep_for(tick.sleepFor);
|
||||
continue;
|
||||
}
|
||||
|
||||
TryCommitReadyRuntimeShader(runtimeRenderScene);
|
||||
const GLuint videoInputTexture = inputTexture.PollAndUpload(mInputMailbox);
|
||||
PublishInputMetrics(inputTexture);
|
||||
if (!readback.RenderAndQueue(frameIndex, [this, &renderer, &runtimeRenderScene, videoInputTexture](uint64_t index) {
|
||||
if (runtimeRenderScene.HasLayers())
|
||||
runtimeRenderScene.RenderFrame(index, mConfig.width, mConfig.height, videoInputTexture);
|
||||
else if (videoInputTexture != 0)
|
||||
renderer.RenderTexture(videoInputTexture);
|
||||
else
|
||||
renderer.RenderFrame(index);
|
||||
}))
|
||||
{
|
||||
mPboQueueMisses.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
PublishReadbackMetrics(readback);
|
||||
|
||||
CountRendered();
|
||||
++frameIndex;
|
||||
clock.MarkRendered(RenderCadenceClock::Clock::now());
|
||||
|
||||
mClockOverruns.store(clock.OverrunCount(), std::memory_order_relaxed);
|
||||
mSkippedFrames.store(clock.SkippedFrameCount(), std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < mConfig.pboDepth * 2; ++i)
|
||||
{
|
||||
readback.ConsumeCompleted(
|
||||
[this](SystemFrame& frame) { return mFrameExchange.AcquireForRender(frame); },
|
||||
[this](const SystemFrame& frame) { return mFrameExchange.PublishCompleted(frame); },
|
||||
[this]() {
|
||||
CountAcquireMiss();
|
||||
},
|
||||
[this]() { CountCompleted(); });
|
||||
PublishReadbackMetrics(readback);
|
||||
}
|
||||
|
||||
readback.Shutdown();
|
||||
inputTexture.ShutdownGl();
|
||||
runtimeRenderScene.ShutdownGl();
|
||||
renderer.ShutdownGl();
|
||||
window.ClearCurrent();
|
||||
mRunning.store(false, std::memory_order_release);
|
||||
RenderCadenceCompositor::TryLog(RenderCadenceCompositor::LogLevel::Log, "render-thread", "Render thread stopped.");
|
||||
}
|
||||
|
||||
void RenderThread::SignalStarted()
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mStartupMutex);
|
||||
mStarted = true;
|
||||
mStartupCondition.notify_all();
|
||||
}
|
||||
|
||||
void RenderThread::SignalStartupFailure(const std::string& error)
|
||||
{
|
||||
RenderCadenceCompositor::TryLog(RenderCadenceCompositor::LogLevel::Error, "render-thread", error);
|
||||
std::lock_guard<std::mutex> lock(mStartupMutex);
|
||||
mStartupError = error;
|
||||
mStartupCondition.notify_all();
|
||||
}
|
||||
|
||||
void RenderThread::CountRendered()
|
||||
{
|
||||
mRenderedFrames.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void RenderThread::CountCompleted()
|
||||
{
|
||||
mCompletedReadbacks.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void RenderThread::CountAcquireMiss()
|
||||
{
|
||||
mAcquireMisses.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void RenderThread::PublishReadbackMetrics(const Bgra8ReadbackPipeline& readback)
|
||||
{
|
||||
const double renderMilliseconds = readback.LastRenderFrameMilliseconds();
|
||||
mRenderFrameMilliseconds.store(renderMilliseconds, std::memory_order_relaxed);
|
||||
if (mConfig.frameDurationMilliseconds > 0.0)
|
||||
{
|
||||
mRenderFrameBudgetUsedPercent.store(
|
||||
(renderMilliseconds / mConfig.frameDurationMilliseconds) * 100.0,
|
||||
std::memory_order_relaxed);
|
||||
}
|
||||
else
|
||||
{
|
||||
mRenderFrameBudgetUsedPercent.store(0.0, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
const double previousMax = mRenderFrameMaxMilliseconds.load(std::memory_order_relaxed);
|
||||
if (renderMilliseconds > previousMax)
|
||||
mRenderFrameMaxMilliseconds.store(renderMilliseconds, std::memory_order_relaxed);
|
||||
|
||||
mReadbackQueueMilliseconds.store(readback.LastReadbackQueueMilliseconds(), std::memory_order_relaxed);
|
||||
mCompletedReadbackCopyMilliseconds.store(readback.LastCompletedReadbackCopyMilliseconds(), std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void RenderThread::PublishInputMetrics(const InputFrameTexture& inputTexture)
|
||||
{
|
||||
if (mInputMailbox != nullptr)
|
||||
{
|
||||
const InputFrameMailboxMetrics mailboxMetrics = mInputMailbox->Metrics();
|
||||
mInputFramesReceived.store(mailboxMetrics.submittedFrames, std::memory_order_relaxed);
|
||||
mInputFramesDropped.store(mailboxMetrics.droppedReadyFrames + mailboxMetrics.submitMisses, std::memory_order_relaxed);
|
||||
mInputConsumeMisses.store(mailboxMetrics.consumeMisses, std::memory_order_relaxed);
|
||||
mInputReadyFrames.store(mailboxMetrics.readyCount, std::memory_order_relaxed);
|
||||
mInputReadingFrames.store(mailboxMetrics.readingCount, std::memory_order_relaxed);
|
||||
mInputLatestAgeMilliseconds.store(mailboxMetrics.latestFrameAgeMilliseconds, std::memory_order_relaxed);
|
||||
mInputSignalPresent.store(mailboxMetrics.hasSubmittedFrame, std::memory_order_relaxed);
|
||||
}
|
||||
else
|
||||
{
|
||||
mInputFramesReceived.store(0, std::memory_order_relaxed);
|
||||
mInputFramesDropped.store(0, std::memory_order_relaxed);
|
||||
mInputConsumeMisses.store(0, std::memory_order_relaxed);
|
||||
mInputReadyFrames.store(0, std::memory_order_relaxed);
|
||||
mInputReadingFrames.store(0, std::memory_order_relaxed);
|
||||
mInputLatestAgeMilliseconds.store(0.0, std::memory_order_relaxed);
|
||||
mInputSignalPresent.store(false, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
mInputUploadMisses.store(inputTexture.UploadMisses(), std::memory_order_relaxed);
|
||||
mInputUploadMilliseconds.store(inputTexture.LastUploadMilliseconds(), std::memory_order_relaxed);
|
||||
mInputFormatSupported.store(inputTexture.LastFrameFormatSupported(), std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void RenderThread::SubmitRuntimeShaderArtifact(const RuntimeShaderArtifact& artifact)
|
||||
{
|
||||
if (artifact.fragmentShaderSource.empty())
|
||||
return;
|
||||
|
||||
std::lock_guard<std::mutex> lock(mShaderArtifactMutex);
|
||||
mPendingShaderArtifact = artifact;
|
||||
mHasPendingShaderArtifact = true;
|
||||
}
|
||||
|
||||
void RenderThread::SubmitRuntimeRenderLayers(const std::vector<RenderCadenceCompositor::RuntimeRenderLayerModel>& layers)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mRenderLayersMutex);
|
||||
mPendingRenderLayers = layers;
|
||||
mHasPendingRenderLayers = true;
|
||||
}
|
||||
|
||||
bool RenderThread::TryTakePendingRuntimeShaderArtifact(RuntimeShaderArtifact& artifact)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mShaderArtifactMutex);
|
||||
if (!mHasPendingShaderArtifact)
|
||||
return false;
|
||||
|
||||
artifact = std::move(mPendingShaderArtifact);
|
||||
mPendingShaderArtifact = RuntimeShaderArtifact();
|
||||
mHasPendingShaderArtifact = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RenderThread::TryTakePendingRenderLayers(std::vector<RenderCadenceCompositor::RuntimeRenderLayerModel>& layers)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mRenderLayersMutex);
|
||||
if (!mHasPendingRenderLayers)
|
||||
return false;
|
||||
|
||||
layers = std::move(mPendingRenderLayers);
|
||||
mPendingRenderLayers.clear();
|
||||
mHasPendingRenderLayers = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void RenderThread::TryCommitReadyRuntimeShader(RuntimeRenderScene& runtimeRenderScene)
|
||||
{
|
||||
std::vector<RenderCadenceCompositor::RuntimeRenderLayerModel> layers;
|
||||
std::string commitError;
|
||||
if (TryTakePendingRenderLayers(layers))
|
||||
{
|
||||
if (!runtimeRenderScene.CommitRenderLayers(layers, commitError))
|
||||
{
|
||||
RenderCadenceCompositor::TryLog(
|
||||
RenderCadenceCompositor::LogLevel::Error,
|
||||
"render-thread",
|
||||
"Runtime render-layer commit failed: " + commitError);
|
||||
mShaderBuildFailures.fetch_add(1, std::memory_order_relaxed);
|
||||
return;
|
||||
}
|
||||
|
||||
RenderCadenceCompositor::TryLog(
|
||||
RenderCadenceCompositor::LogLevel::Log,
|
||||
"render-thread",
|
||||
"Runtime render layer snapshot committed.");
|
||||
mShaderBuildsCommitted.fetch_add(1, std::memory_order_relaxed);
|
||||
return;
|
||||
}
|
||||
|
||||
RuntimeShaderArtifact artifact;
|
||||
if (!TryTakePendingRuntimeShaderArtifact(artifact))
|
||||
return;
|
||||
|
||||
RenderCadenceCompositor::RuntimeRenderLayerModel layer;
|
||||
layer.id = artifact.layerId.empty() ? "runtime-layer-1" : artifact.layerId;
|
||||
layer.shaderId = artifact.shaderId;
|
||||
layer.artifact = artifact;
|
||||
layers.push_back(std::move(layer));
|
||||
if (!runtimeRenderScene.CommitRenderLayers(layers, commitError))
|
||||
{
|
||||
RenderCadenceCompositor::TryLog(
|
||||
RenderCadenceCompositor::LogLevel::Error,
|
||||
"render-thread",
|
||||
"Runtime shader GL commit failed: " + commitError);
|
||||
mShaderBuildFailures.fetch_add(1, std::memory_order_relaxed);
|
||||
return;
|
||||
}
|
||||
|
||||
RenderCadenceCompositor::TryLog(
|
||||
RenderCadenceCompositor::LogLevel::Log,
|
||||
"render-thread",
|
||||
"Runtime shader committed: " + artifact.shaderId + ". " + artifact.message);
|
||||
mShaderBuildsCommitted.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
129
src/render/RenderThread.h
Normal file
129
src/render/RenderThread.h
Normal file
@@ -0,0 +1,129 @@
|
||||
#pragma once
|
||||
|
||||
#include "RenderCadenceClock.h"
|
||||
#include "../runtime/RuntimeLayerModel.h"
|
||||
#include "../runtime/RuntimeShaderArtifact.h"
|
||||
#include "runtime/RuntimeRenderScene.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
class SystemFrameExchange;
|
||||
class InputFrameMailbox;
|
||||
class InputFrameTexture;
|
||||
class Bgra8ReadbackPipeline;
|
||||
|
||||
class RenderThread
|
||||
{
|
||||
public:
|
||||
struct Config
|
||||
{
|
||||
unsigned width = 1920;
|
||||
unsigned height = 1080;
|
||||
double frameDurationMilliseconds = 1000.0 / 59.94;
|
||||
std::size_t pboDepth = 6;
|
||||
};
|
||||
|
||||
struct Metrics
|
||||
{
|
||||
uint64_t renderedFrames = 0;
|
||||
uint64_t completedReadbacks = 0;
|
||||
uint64_t acquireMisses = 0;
|
||||
uint64_t pboQueueMisses = 0;
|
||||
uint64_t clockOverruns = 0;
|
||||
uint64_t skippedFrames = 0;
|
||||
uint64_t shaderBuildsCommitted = 0;
|
||||
uint64_t shaderBuildFailures = 0;
|
||||
double renderFrameMilliseconds = 0.0;
|
||||
double renderFrameBudgetUsedPercent = 0.0;
|
||||
double renderFrameMaxMilliseconds = 0.0;
|
||||
double readbackQueueMilliseconds = 0.0;
|
||||
double completedReadbackCopyMilliseconds = 0.0;
|
||||
uint64_t inputFramesReceived = 0;
|
||||
uint64_t inputFramesDropped = 0;
|
||||
uint64_t inputConsumeMisses = 0;
|
||||
uint64_t inputUploadMisses = 0;
|
||||
std::size_t inputReadyFrames = 0;
|
||||
std::size_t inputReadingFrames = 0;
|
||||
double inputLatestAgeMilliseconds = 0.0;
|
||||
double inputUploadMilliseconds = 0.0;
|
||||
bool inputFormatSupported = true;
|
||||
bool inputSignalPresent = false;
|
||||
};
|
||||
|
||||
RenderThread(SystemFrameExchange& frameExchange, Config config);
|
||||
RenderThread(SystemFrameExchange& frameExchange, InputFrameMailbox* inputMailbox, Config config);
|
||||
RenderThread(const RenderThread&) = delete;
|
||||
RenderThread& operator=(const RenderThread&) = delete;
|
||||
~RenderThread();
|
||||
|
||||
bool Start(std::string& error);
|
||||
void Stop();
|
||||
void SubmitRuntimeShaderArtifact(const RuntimeShaderArtifact& artifact);
|
||||
void SubmitRuntimeRenderLayers(const std::vector<RenderCadenceCompositor::RuntimeRenderLayerModel>& layers);
|
||||
|
||||
Metrics GetMetrics() const;
|
||||
bool IsRunning() const { return mRunning.load(std::memory_order_acquire); }
|
||||
|
||||
private:
|
||||
void ThreadMain();
|
||||
void SignalStarted();
|
||||
void SignalStartupFailure(const std::string& error);
|
||||
void CountRendered();
|
||||
void CountCompleted();
|
||||
void CountAcquireMiss();
|
||||
void PublishReadbackMetrics(const Bgra8ReadbackPipeline& readback);
|
||||
void PublishInputMetrics(const InputFrameTexture& inputTexture);
|
||||
void TryCommitReadyRuntimeShader(RuntimeRenderScene& runtimeRenderScene);
|
||||
bool TryTakePendingRuntimeShaderArtifact(RuntimeShaderArtifact& artifact);
|
||||
bool TryTakePendingRenderLayers(std::vector<RenderCadenceCompositor::RuntimeRenderLayerModel>& layers);
|
||||
|
||||
SystemFrameExchange& mFrameExchange;
|
||||
InputFrameMailbox* mInputMailbox = nullptr;
|
||||
Config mConfig;
|
||||
std::thread mThread;
|
||||
std::atomic<bool> mStopping{ false };
|
||||
std::atomic<bool> mRunning{ false };
|
||||
|
||||
mutable std::mutex mStartupMutex;
|
||||
std::condition_variable mStartupCondition;
|
||||
bool mStarted = false;
|
||||
std::string mStartupError;
|
||||
|
||||
std::atomic<uint64_t> mRenderedFrames{ 0 };
|
||||
std::atomic<uint64_t> mCompletedReadbacks{ 0 };
|
||||
std::atomic<uint64_t> mAcquireMisses{ 0 };
|
||||
std::atomic<uint64_t> mPboQueueMisses{ 0 };
|
||||
std::atomic<uint64_t> mClockOverruns{ 0 };
|
||||
std::atomic<uint64_t> mSkippedFrames{ 0 };
|
||||
std::atomic<uint64_t> mShaderBuildsCommitted{ 0 };
|
||||
std::atomic<uint64_t> mShaderBuildFailures{ 0 };
|
||||
std::atomic<double> mRenderFrameMilliseconds{ 0.0 };
|
||||
std::atomic<double> mRenderFrameBudgetUsedPercent{ 0.0 };
|
||||
std::atomic<double> mRenderFrameMaxMilliseconds{ 0.0 };
|
||||
std::atomic<double> mReadbackQueueMilliseconds{ 0.0 };
|
||||
std::atomic<double> mCompletedReadbackCopyMilliseconds{ 0.0 };
|
||||
std::atomic<uint64_t> mInputFramesReceived{ 0 };
|
||||
std::atomic<uint64_t> mInputFramesDropped{ 0 };
|
||||
std::atomic<uint64_t> mInputConsumeMisses{ 0 };
|
||||
std::atomic<uint64_t> mInputUploadMisses{ 0 };
|
||||
std::atomic<std::size_t> mInputReadyFrames{ 0 };
|
||||
std::atomic<std::size_t> mInputReadingFrames{ 0 };
|
||||
std::atomic<double> mInputLatestAgeMilliseconds{ 0.0 };
|
||||
std::atomic<double> mInputUploadMilliseconds{ 0.0 };
|
||||
std::atomic<bool> mInputFormatSupported{ true };
|
||||
std::atomic<bool> mInputSignalPresent{ false };
|
||||
|
||||
std::mutex mShaderArtifactMutex;
|
||||
bool mHasPendingShaderArtifact = false;
|
||||
RuntimeShaderArtifact mPendingShaderArtifact;
|
||||
|
||||
std::mutex mRenderLayersMutex;
|
||||
bool mHasPendingRenderLayers = false;
|
||||
std::vector<RenderCadenceCompositor::RuntimeRenderLayerModel> mPendingRenderLayers;
|
||||
};
|
||||
313
src/render/SimpleMotionRenderer.cpp
Normal file
313
src/render/SimpleMotionRenderer.cpp
Normal file
@@ -0,0 +1,313 @@
|
||||
#include "SimpleMotionRenderer.h"
|
||||
|
||||
#include "GLExtensions.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr GLuint kInputTextureUnit = 0;
|
||||
|
||||
const char* kTextureVertexShader = R"GLSL(
|
||||
#version 430 core
|
||||
out vec2 vTexCoord;
|
||||
void main()
|
||||
{
|
||||
vec2 positions[3] = vec2[3](
|
||||
vec2(-1.0, -1.0),
|
||||
vec2( 3.0, -1.0),
|
||||
vec2(-1.0, 3.0));
|
||||
vec2 texCoords[3] = vec2[3](
|
||||
vec2(0.0, 0.0),
|
||||
vec2(2.0, 0.0),
|
||||
vec2(0.0, 2.0));
|
||||
gl_Position = vec4(positions[gl_VertexID], 0.0, 1.0);
|
||||
vTexCoord = texCoords[gl_VertexID];
|
||||
}
|
||||
)GLSL";
|
||||
|
||||
const char* kTextureFragmentShader = R"GLSL(
|
||||
#version 430 core
|
||||
layout(binding = 0) uniform sampler2D uInputTexture;
|
||||
in vec2 vTexCoord;
|
||||
out vec4 fragColor;
|
||||
void main()
|
||||
{
|
||||
fragColor = texture(uInputTexture, clamp(vTexCoord, vec2(0.0), vec2(1.0)));
|
||||
}
|
||||
)GLSL";
|
||||
|
||||
const char* kPatternFragmentShader = R"GLSL(
|
||||
#version 430 core
|
||||
uniform vec2 uResolution;
|
||||
uniform float uFrameIndex;
|
||||
in vec2 vTexCoord;
|
||||
out vec4 fragColor;
|
||||
|
||||
vec3 hexColor(float r, float g, float b)
|
||||
{
|
||||
return vec3(r, g, b) / 255.0;
|
||||
}
|
||||
|
||||
vec3 smpteTop(float x)
|
||||
{
|
||||
if (x < 240.0) return hexColor(102.0, 102.0, 102.0);
|
||||
if (x < 445.0) return hexColor(191.0, 191.0, 191.0);
|
||||
if (x < 651.0) return hexColor(191.0, 191.0, 0.0);
|
||||
if (x < 857.0) return hexColor(0.0, 191.0, 191.0);
|
||||
if (x < 1063.0) return hexColor(0.0, 191.0, 0.0);
|
||||
if (x < 1269.0) return hexColor(191.0, 0.0, 191.0);
|
||||
if (x < 1475.0) return hexColor(191.0, 0.0, 0.0);
|
||||
if (x < 1680.0) return hexColor(0.0, 0.0, 191.0);
|
||||
return hexColor(102.0, 102.0, 102.0);
|
||||
}
|
||||
|
||||
vec3 smpteMiddleA(float x)
|
||||
{
|
||||
if (x < 240.0) return hexColor(0.0, 255.0, 255.0);
|
||||
if (x < 445.0) return hexColor(0.0, 63.0, 105.0);
|
||||
if (x < 1680.0) return hexColor(191.0, 191.0, 191.0);
|
||||
return hexColor(0.0, 0.0, 255.0);
|
||||
}
|
||||
|
||||
vec3 smpteMiddleB(float x)
|
||||
{
|
||||
if (x < 240.0) return hexColor(255.0, 255.0, 0.0);
|
||||
if (x < 445.0) return hexColor(65.0, 0.0, 119.0);
|
||||
if (x < 1475.0)
|
||||
{
|
||||
float ramp = clamp((x - 445.0) / (1475.0 - 445.0), 0.0, 1.0);
|
||||
return vec3(ramp);
|
||||
}
|
||||
if (x < 1680.0) return vec3(1.0);
|
||||
return hexColor(255.0, 0.0, 0.0);
|
||||
}
|
||||
|
||||
vec3 smpteBottom(float x)
|
||||
{
|
||||
if (x < 240.0) return hexColor(38.0, 38.0, 38.0);
|
||||
if (x < 549.0) return vec3(0.0);
|
||||
if (x < 960.0) return vec3(1.0);
|
||||
if (x < 1268.0) return vec3(0.0);
|
||||
if (x < 1337.0) return hexColor(5.0, 5.0, 5.0);
|
||||
if (x < 1405.0) return vec3(0.0);
|
||||
if (x < 1474.0) return hexColor(10.0, 10.0, 10.0);
|
||||
if (x < 1680.0) return vec3(0.0);
|
||||
return hexColor(38.0, 38.0, 38.0);
|
||||
}
|
||||
|
||||
vec3 smpteColor(vec2 uv)
|
||||
{
|
||||
vec2 pixel = vec2(clamp(uv.x, 0.0, 1.0), 1.0 - clamp(uv.y, 0.0, 1.0)) * vec2(1920.0, 1080.0);
|
||||
if (pixel.y < 630.0) return smpteTop(pixel.x);
|
||||
if (pixel.y < 720.0) return smpteMiddleA(pixel.x);
|
||||
if (pixel.y < 810.0) return smpteMiddleB(pixel.x);
|
||||
return smpteBottom(pixel.x);
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
vec2 uv = clamp(vTexCoord, vec2(0.0), vec2(1.0));
|
||||
vec3 color = smpteColor(uv);
|
||||
|
||||
float t = uFrameIndex / 60.0;
|
||||
vec2 cubeSize = vec2(0.16, 0.20);
|
||||
vec2 cubeMin = vec2(
|
||||
(0.5 + 0.5 * sin(t * 1.7)) * (1.0 - cubeSize.x),
|
||||
(0.5 + 0.5 * sin(t * 1.1 + 0.8)) * (1.0 - cubeSize.y));
|
||||
vec2 cubeMax = cubeMin + cubeSize;
|
||||
bool insideCube = uv.x >= cubeMin.x && uv.x <= cubeMax.x && uv.y >= cubeMin.y && uv.y <= cubeMax.y;
|
||||
if (insideCube)
|
||||
{
|
||||
vec2 local = (uv - cubeMin) / cubeSize;
|
||||
vec3 cubeColor = vec3(1.0, 0.74 + 0.18 * sin(t * 2.1), 0.08);
|
||||
float edge = step(local.x, 0.04) + step(local.y, 0.04) + step(0.96, local.x) + step(0.96, local.y);
|
||||
color = edge > 0.0 ? vec3(1.0) : cubeColor;
|
||||
}
|
||||
|
||||
fragColor = vec4(color, 1.0);
|
||||
}
|
||||
)GLSL";
|
||||
}
|
||||
|
||||
bool SimpleMotionRenderer::InitializeGl(unsigned width, unsigned height)
|
||||
{
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
return mWidth > 0 && mHeight > 0;
|
||||
}
|
||||
|
||||
void SimpleMotionRenderer::RenderFrame(uint64_t frameIndex)
|
||||
{
|
||||
if (!EnsurePatternProgram())
|
||||
{
|
||||
glViewport(0, 0, static_cast<GLsizei>(mWidth), static_cast<GLsizei>(mHeight));
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
return;
|
||||
}
|
||||
|
||||
glViewport(0, 0, static_cast<GLsizei>(mWidth), static_cast<GLsizei>(mHeight));
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glDisable(GL_BLEND);
|
||||
glUseProgram(mPatternProgram);
|
||||
const GLint resolutionLocation = glGetUniformLocation(mPatternProgram, "uResolution");
|
||||
if (resolutionLocation >= 0)
|
||||
glUniform2f(resolutionLocation, static_cast<float>(mWidth), static_cast<float>(mHeight));
|
||||
const GLint frameIndexLocation = glGetUniformLocation(mPatternProgram, "uFrameIndex");
|
||||
if (frameIndexLocation >= 0)
|
||||
glUniform1f(frameIndexLocation, static_cast<float>(frameIndex));
|
||||
glBindVertexArray(mPatternVertexArray);
|
||||
glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||
glBindVertexArray(0);
|
||||
glUseProgram(0);
|
||||
}
|
||||
|
||||
void SimpleMotionRenderer::RenderTexture(GLuint texture)
|
||||
{
|
||||
if (texture == 0 || !EnsureTextureProgram())
|
||||
{
|
||||
RenderFrame(0);
|
||||
return;
|
||||
}
|
||||
|
||||
glViewport(0, 0, static_cast<GLsizei>(mWidth), static_cast<GLsizei>(mHeight));
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glDisable(GL_BLEND);
|
||||
glActiveTexture(GL_TEXTURE0 + kInputTextureUnit);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
glUseProgram(mTextureProgram);
|
||||
glBindVertexArray(mTextureVertexArray);
|
||||
glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||
glBindVertexArray(0);
|
||||
glUseProgram(0);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
}
|
||||
|
||||
void SimpleMotionRenderer::ShutdownGl()
|
||||
{
|
||||
DestroyPatternProgram();
|
||||
DestroyTextureProgram();
|
||||
mWidth = 0;
|
||||
mHeight = 0;
|
||||
}
|
||||
|
||||
bool SimpleMotionRenderer::EnsurePatternProgram()
|
||||
{
|
||||
if (mPatternProgram != 0)
|
||||
return true;
|
||||
|
||||
if (!CompileShader(GL_VERTEX_SHADER, kTextureVertexShader, mPatternVertexShader))
|
||||
return false;
|
||||
if (!CompileShader(GL_FRAGMENT_SHADER, kPatternFragmentShader, mPatternFragmentShader))
|
||||
{
|
||||
DestroyPatternProgram();
|
||||
return false;
|
||||
}
|
||||
|
||||
mPatternProgram = glCreateProgram();
|
||||
glAttachShader(mPatternProgram, mPatternVertexShader);
|
||||
glAttachShader(mPatternProgram, mPatternFragmentShader);
|
||||
glLinkProgram(mPatternProgram);
|
||||
|
||||
GLint linkResult = GL_FALSE;
|
||||
glGetProgramiv(mPatternProgram, GL_LINK_STATUS, &linkResult);
|
||||
if (linkResult == GL_FALSE)
|
||||
{
|
||||
DestroyPatternProgram();
|
||||
return false;
|
||||
}
|
||||
|
||||
glGenVertexArrays(1, &mPatternVertexArray);
|
||||
return mPatternVertexArray != 0;
|
||||
}
|
||||
|
||||
bool SimpleMotionRenderer::EnsureTextureProgram()
|
||||
{
|
||||
if (mTextureProgram != 0)
|
||||
return true;
|
||||
|
||||
if (!CompileShader(GL_VERTEX_SHADER, kTextureVertexShader, mTextureVertexShader))
|
||||
return false;
|
||||
if (!CompileShader(GL_FRAGMENT_SHADER, kTextureFragmentShader, mTextureFragmentShader))
|
||||
{
|
||||
DestroyTextureProgram();
|
||||
return false;
|
||||
}
|
||||
|
||||
mTextureProgram = glCreateProgram();
|
||||
glAttachShader(mTextureProgram, mTextureVertexShader);
|
||||
glAttachShader(mTextureProgram, mTextureFragmentShader);
|
||||
glLinkProgram(mTextureProgram);
|
||||
|
||||
GLint linkResult = GL_FALSE;
|
||||
glGetProgramiv(mTextureProgram, GL_LINK_STATUS, &linkResult);
|
||||
if (linkResult == GL_FALSE)
|
||||
{
|
||||
DestroyTextureProgram();
|
||||
return false;
|
||||
}
|
||||
|
||||
glUseProgram(mTextureProgram);
|
||||
const GLint inputLocation = glGetUniformLocation(mTextureProgram, "uInputTexture");
|
||||
if (inputLocation >= 0)
|
||||
glUniform1i(inputLocation, static_cast<GLint>(kInputTextureUnit));
|
||||
glUseProgram(0);
|
||||
|
||||
glGenVertexArrays(1, &mTextureVertexArray);
|
||||
return mTextureVertexArray != 0;
|
||||
}
|
||||
|
||||
void SimpleMotionRenderer::DestroyTextureProgram()
|
||||
{
|
||||
if (mTextureProgram != 0)
|
||||
glDeleteProgram(mTextureProgram);
|
||||
if (mTextureVertexShader != 0)
|
||||
glDeleteShader(mTextureVertexShader);
|
||||
if (mTextureFragmentShader != 0)
|
||||
glDeleteShader(mTextureFragmentShader);
|
||||
if (mTextureVertexArray != 0)
|
||||
glDeleteVertexArrays(1, &mTextureVertexArray);
|
||||
mTextureProgram = 0;
|
||||
mTextureVertexShader = 0;
|
||||
mTextureFragmentShader = 0;
|
||||
mTextureVertexArray = 0;
|
||||
}
|
||||
|
||||
void SimpleMotionRenderer::DestroyPatternProgram()
|
||||
{
|
||||
if (mPatternProgram != 0)
|
||||
glDeleteProgram(mPatternProgram);
|
||||
if (mPatternVertexShader != 0)
|
||||
glDeleteShader(mPatternVertexShader);
|
||||
if (mPatternFragmentShader != 0)
|
||||
glDeleteShader(mPatternFragmentShader);
|
||||
if (mPatternVertexArray != 0)
|
||||
glDeleteVertexArrays(1, &mPatternVertexArray);
|
||||
mPatternProgram = 0;
|
||||
mPatternVertexShader = 0;
|
||||
mPatternFragmentShader = 0;
|
||||
mPatternVertexArray = 0;
|
||||
}
|
||||
|
||||
bool SimpleMotionRenderer::CompileShader(GLenum shaderType, const char* source, GLuint& shader)
|
||||
{
|
||||
shader = glCreateShader(shaderType);
|
||||
glShaderSource(shader, 1, &source, nullptr);
|
||||
glCompileShader(shader);
|
||||
|
||||
GLint compileResult = GL_FALSE;
|
||||
glGetShaderiv(shader, GL_COMPILE_STATUS, &compileResult);
|
||||
if (compileResult != GL_FALSE)
|
||||
return true;
|
||||
|
||||
glDeleteShader(shader);
|
||||
shader = 0;
|
||||
return false;
|
||||
}
|
||||
37
src/render/SimpleMotionRenderer.h
Normal file
37
src/render/SimpleMotionRenderer.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include "GLExtensions.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
class SimpleMotionRenderer
|
||||
{
|
||||
public:
|
||||
SimpleMotionRenderer() = default;
|
||||
|
||||
bool InitializeGl(unsigned width, unsigned height);
|
||||
void RenderFrame(uint64_t frameIndex);
|
||||
void RenderTexture(GLuint texture);
|
||||
void ShutdownGl();
|
||||
|
||||
unsigned Width() const { return mWidth; }
|
||||
unsigned Height() const { return mHeight; }
|
||||
|
||||
private:
|
||||
bool EnsureTextureProgram();
|
||||
bool EnsurePatternProgram();
|
||||
void DestroyTextureProgram();
|
||||
void DestroyPatternProgram();
|
||||
static bool CompileShader(GLenum shaderType, const char* source, GLuint& shader);
|
||||
|
||||
unsigned mWidth = 0;
|
||||
unsigned mHeight = 0;
|
||||
GLuint mPatternProgram = 0;
|
||||
GLuint mPatternVertexShader = 0;
|
||||
GLuint mPatternFragmentShader = 0;
|
||||
GLuint mPatternVertexArray = 0;
|
||||
GLuint mTextureProgram = 0;
|
||||
GLuint mTextureVertexShader = 0;
|
||||
GLuint mTextureFragmentShader = 0;
|
||||
GLuint mTextureVertexArray = 0;
|
||||
};
|
||||
160
src/render/readback/Bgra8ReadbackPipeline.cpp
Normal file
160
src/render/readback/Bgra8ReadbackPipeline.cpp
Normal file
@@ -0,0 +1,160 @@
|
||||
#include "Bgra8ReadbackPipeline.h"
|
||||
|
||||
#include "../frames/SystemFrameTypes.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
|
||||
namespace
|
||||
{
|
||||
double MillisecondsSince(std::chrono::steady_clock::time_point start)
|
||||
{
|
||||
return std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(
|
||||
std::chrono::steady_clock::now() - start).count();
|
||||
}
|
||||
}
|
||||
|
||||
Bgra8ReadbackPipeline::~Bgra8ReadbackPipeline()
|
||||
{
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
bool Bgra8ReadbackPipeline::Initialize(unsigned width, unsigned height, std::size_t pboDepth)
|
||||
{
|
||||
Shutdown();
|
||||
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
mRowBytes = VideoIORowBytes(VideoIOPixelFormat::Bgra8, width);
|
||||
if (mWidth == 0 || mHeight == 0 || mRowBytes == 0)
|
||||
return false;
|
||||
|
||||
if (!CreateRenderTarget())
|
||||
{
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::size_t byteCount = static_cast<std::size_t>(mRowBytes) * static_cast<std::size_t>(mHeight);
|
||||
if (!mPboRing.Initialize(pboDepth, byteCount))
|
||||
{
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Bgra8ReadbackPipeline::Shutdown()
|
||||
{
|
||||
mPboRing.Shutdown();
|
||||
DestroyRenderTarget();
|
||||
mWidth = 0;
|
||||
mHeight = 0;
|
||||
mRowBytes = 0;
|
||||
}
|
||||
|
||||
bool Bgra8ReadbackPipeline::RenderAndQueue(uint64_t frameIndex, const RenderCallback& renderFrame)
|
||||
{
|
||||
if (mFramebuffer == 0 || !renderFrame)
|
||||
return false;
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
|
||||
const auto renderStart = std::chrono::steady_clock::now();
|
||||
renderFrame(frameIndex);
|
||||
mLastRenderFrameMilliseconds = MillisecondsSince(renderStart);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
|
||||
const auto queueStart = std::chrono::steady_clock::now();
|
||||
const bool queued = mPboRing.QueueReadback(mFramebuffer, mWidth, mHeight, frameIndex);
|
||||
mLastReadbackQueueMilliseconds = MillisecondsSince(queueStart);
|
||||
return queued;
|
||||
}
|
||||
|
||||
void Bgra8ReadbackPipeline::ConsumeCompleted(
|
||||
const AcquireFrameCallback& acquireFrame,
|
||||
const PublishFrameCallback& publishFrame,
|
||||
const CounterCallback& onAcquireMiss,
|
||||
const CounterCallback& onCompleted)
|
||||
{
|
||||
if (!acquireFrame || !publishFrame)
|
||||
return;
|
||||
|
||||
PboReadbackRing::CompletedReadback readback;
|
||||
while (mPboRing.TryAcquireCompleted(readback))
|
||||
{
|
||||
const auto copyStart = std::chrono::steady_clock::now();
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, readback.pbo);
|
||||
void* mapped = glMapBuffer(GL_PIXEL_PACK_BUFFER, GL_READ_ONLY);
|
||||
if (!mapped)
|
||||
{
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||
mPboRing.ReleaseCompleted(readback);
|
||||
mLastCompletedReadbackCopyMilliseconds = MillisecondsSince(copyStart);
|
||||
continue;
|
||||
}
|
||||
|
||||
SystemFrame frame;
|
||||
if (acquireFrame(frame))
|
||||
{
|
||||
const std::size_t byteCount = static_cast<std::size_t>(frame.rowBytes) * static_cast<std::size_t>(frame.height);
|
||||
if (frame.bytes != nullptr && byteCount <= readback.byteCount)
|
||||
{
|
||||
std::memcpy(frame.bytes, mapped, byteCount);
|
||||
frame.frameIndex = readback.frameIndex;
|
||||
frame.pixelFormat = VideoIOPixelFormat::Bgra8;
|
||||
publishFrame(frame);
|
||||
if (onCompleted)
|
||||
onCompleted();
|
||||
}
|
||||
}
|
||||
else if (onAcquireMiss)
|
||||
{
|
||||
onAcquireMiss();
|
||||
}
|
||||
|
||||
glUnmapBuffer(GL_PIXEL_PACK_BUFFER);
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||
mPboRing.ReleaseCompleted(readback);
|
||||
mLastCompletedReadbackCopyMilliseconds = MillisecondsSince(copyStart);
|
||||
}
|
||||
}
|
||||
|
||||
bool Bgra8ReadbackPipeline::CreateRenderTarget()
|
||||
{
|
||||
glGenFramebuffers(1, &mFramebuffer);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, mFramebuffer);
|
||||
|
||||
glGenTextures(1, &mTexture);
|
||||
glBindTexture(GL_TEXTURE_2D, mTexture);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexImage2D(
|
||||
GL_TEXTURE_2D,
|
||||
0,
|
||||
GL_RGBA8,
|
||||
static_cast<GLsizei>(mWidth),
|
||||
static_cast<GLsizei>(mHeight),
|
||||
0,
|
||||
GL_BGRA,
|
||||
GL_UNSIGNED_INT_8_8_8_8_REV,
|
||||
nullptr);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTexture, 0);
|
||||
|
||||
const bool complete = glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE;
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
return complete;
|
||||
}
|
||||
|
||||
void Bgra8ReadbackPipeline::DestroyRenderTarget()
|
||||
{
|
||||
if (mFramebuffer != 0)
|
||||
glDeleteFramebuffers(1, &mFramebuffer);
|
||||
if (mTexture != 0)
|
||||
glDeleteTextures(1, &mTexture);
|
||||
mFramebuffer = 0;
|
||||
mTexture = 0;
|
||||
}
|
||||
58
src/render/readback/Bgra8ReadbackPipeline.h
Normal file
58
src/render/readback/Bgra8ReadbackPipeline.h
Normal file
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include "PboReadbackRing.h"
|
||||
#include "VideoIOFormat.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
|
||||
struct SystemFrame;
|
||||
|
||||
class Bgra8ReadbackPipeline
|
||||
{
|
||||
public:
|
||||
using RenderCallback = std::function<void(uint64_t frameIndex)>;
|
||||
using AcquireFrameCallback = std::function<bool(SystemFrame& frame)>;
|
||||
using PublishFrameCallback = std::function<bool(const SystemFrame& frame)>;
|
||||
using CounterCallback = std::function<void()>;
|
||||
|
||||
Bgra8ReadbackPipeline() = default;
|
||||
Bgra8ReadbackPipeline(const Bgra8ReadbackPipeline&) = delete;
|
||||
Bgra8ReadbackPipeline& operator=(const Bgra8ReadbackPipeline&) = delete;
|
||||
~Bgra8ReadbackPipeline();
|
||||
|
||||
bool Initialize(unsigned width, unsigned height, std::size_t pboDepth);
|
||||
void Shutdown();
|
||||
|
||||
bool RenderAndQueue(uint64_t frameIndex, const RenderCallback& renderFrame);
|
||||
void ConsumeCompleted(
|
||||
const AcquireFrameCallback& acquireFrame,
|
||||
const PublishFrameCallback& publishFrame,
|
||||
const CounterCallback& onAcquireMiss = {},
|
||||
const CounterCallback& onCompleted = {});
|
||||
|
||||
GLuint Framebuffer() const { return mFramebuffer; }
|
||||
unsigned Width() const { return mWidth; }
|
||||
unsigned Height() const { return mHeight; }
|
||||
unsigned RowBytes() const { return mRowBytes; }
|
||||
VideoIOPixelFormat PixelFormat() const { return VideoIOPixelFormat::Bgra8; }
|
||||
uint64_t PboQueueMisses() const { return mPboRing.QueueMisses(); }
|
||||
double LastRenderFrameMilliseconds() const { return mLastRenderFrameMilliseconds; }
|
||||
double LastReadbackQueueMilliseconds() const { return mLastReadbackQueueMilliseconds; }
|
||||
double LastCompletedReadbackCopyMilliseconds() const { return mLastCompletedReadbackCopyMilliseconds; }
|
||||
|
||||
private:
|
||||
bool CreateRenderTarget();
|
||||
void DestroyRenderTarget();
|
||||
|
||||
unsigned mWidth = 0;
|
||||
unsigned mHeight = 0;
|
||||
unsigned mRowBytes = 0;
|
||||
GLuint mFramebuffer = 0;
|
||||
GLuint mTexture = 0;
|
||||
double mLastRenderFrameMilliseconds = 0.0;
|
||||
double mLastReadbackQueueMilliseconds = 0.0;
|
||||
double mLastCompletedReadbackCopyMilliseconds = 0.0;
|
||||
PboReadbackRing mPboRing;
|
||||
};
|
||||
138
src/render/readback/PboReadbackRing.cpp
Normal file
138
src/render/readback/PboReadbackRing.cpp
Normal file
@@ -0,0 +1,138 @@
|
||||
#include "PboReadbackRing.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
PboReadbackRing::~PboReadbackRing()
|
||||
{
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
bool PboReadbackRing::Initialize(std::size_t depth, std::size_t byteCount)
|
||||
{
|
||||
Shutdown();
|
||||
if (depth == 0 || byteCount == 0)
|
||||
return false;
|
||||
|
||||
mSlots.resize(depth);
|
||||
mByteCount = byteCount;
|
||||
for (Slot& slot : mSlots)
|
||||
{
|
||||
glGenBuffers(1, &slot.pbo);
|
||||
if (slot.pbo == 0)
|
||||
{
|
||||
Shutdown();
|
||||
return false;
|
||||
}
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, slot.pbo);
|
||||
glBufferData(GL_PIXEL_PACK_BUFFER, static_cast<GLsizeiptr>(mByteCount), nullptr, GL_STREAM_READ);
|
||||
}
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
void PboReadbackRing::Shutdown()
|
||||
{
|
||||
for (Slot& slot : mSlots)
|
||||
{
|
||||
if (slot.fence)
|
||||
glDeleteSync(slot.fence);
|
||||
if (slot.pbo != 0)
|
||||
glDeleteBuffers(1, &slot.pbo);
|
||||
slot = {};
|
||||
}
|
||||
mSlots.clear();
|
||||
mWriteIndex = 0;
|
||||
mReadIndex = 0;
|
||||
mByteCount = 0;
|
||||
}
|
||||
|
||||
bool PboReadbackRing::QueueReadback(GLuint framebuffer, unsigned width, unsigned height, uint64_t frameIndex)
|
||||
{
|
||||
if (mSlots.empty())
|
||||
return false;
|
||||
|
||||
Slot& slot = mSlots[mWriteIndex];
|
||||
if (slot.inFlight || slot.acquired)
|
||||
{
|
||||
++mQueueMisses;
|
||||
return false;
|
||||
}
|
||||
|
||||
glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffer);
|
||||
glPixelStorei(GL_PACK_ALIGNMENT, 4);
|
||||
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, slot.pbo);
|
||||
glBufferData(GL_PIXEL_PACK_BUFFER, static_cast<GLsizeiptr>(mByteCount), nullptr, GL_STREAM_READ);
|
||||
glReadPixels(0, 0, static_cast<GLsizei>(width), static_cast<GLsizei>(height), GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, nullptr);
|
||||
slot.fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
|
||||
slot.inFlight = slot.fence != nullptr;
|
||||
slot.frameIndex = frameIndex;
|
||||
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
|
||||
|
||||
if (!slot.inFlight)
|
||||
return false;
|
||||
|
||||
mWriteIndex = (mWriteIndex + 1) % mSlots.size();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PboReadbackRing::TryAcquireCompleted(CompletedReadback& readback)
|
||||
{
|
||||
if (mSlots.empty())
|
||||
return false;
|
||||
|
||||
for (std::size_t checked = 0; checked < mSlots.size(); ++checked)
|
||||
{
|
||||
Slot& slot = mSlots[mReadIndex];
|
||||
if (!slot.inFlight || slot.acquired || slot.fence == nullptr)
|
||||
{
|
||||
mReadIndex = (mReadIndex + 1) % mSlots.size();
|
||||
continue;
|
||||
}
|
||||
|
||||
const GLenum waitResult = glClientWaitSync(slot.fence, 0, 0);
|
||||
if (waitResult != GL_ALREADY_SIGNALED && waitResult != GL_CONDITION_SATISFIED)
|
||||
return false;
|
||||
|
||||
slot.acquired = true;
|
||||
readback.pbo = slot.pbo;
|
||||
readback.frameIndex = slot.frameIndex;
|
||||
readback.byteCount = mByteCount;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void PboReadbackRing::ReleaseCompleted(const CompletedReadback& readback)
|
||||
{
|
||||
for (std::size_t index = 0; index < mSlots.size(); ++index)
|
||||
{
|
||||
Slot& slot = mSlots[index];
|
||||
if (!slot.acquired || slot.pbo != readback.pbo)
|
||||
continue;
|
||||
ResetSlot(slot);
|
||||
mReadIndex = (index + 1) % mSlots.size();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void PboReadbackRing::DrainCompleted()
|
||||
{
|
||||
for (std::size_t pass = 0; pass < mSlots.size() * 2; ++pass)
|
||||
{
|
||||
CompletedReadback readback;
|
||||
if (!TryAcquireCompleted(readback))
|
||||
break;
|
||||
ReleaseCompleted(readback);
|
||||
}
|
||||
}
|
||||
|
||||
void PboReadbackRing::ResetSlot(Slot& slot)
|
||||
{
|
||||
if (slot.fence)
|
||||
glDeleteSync(slot.fence);
|
||||
slot.fence = nullptr;
|
||||
slot.inFlight = false;
|
||||
slot.acquired = false;
|
||||
slot.frameIndex = 0;
|
||||
}
|
||||
52
src/render/readback/PboReadbackRing.h
Normal file
52
src/render/readback/PboReadbackRing.h
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include "GLExtensions.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
class PboReadbackRing
|
||||
{
|
||||
public:
|
||||
struct CompletedReadback
|
||||
{
|
||||
GLuint pbo = 0;
|
||||
uint64_t frameIndex = 0;
|
||||
std::size_t byteCount = 0;
|
||||
};
|
||||
|
||||
PboReadbackRing() = default;
|
||||
PboReadbackRing(const PboReadbackRing&) = delete;
|
||||
PboReadbackRing& operator=(const PboReadbackRing&) = delete;
|
||||
~PboReadbackRing();
|
||||
|
||||
bool Initialize(std::size_t depth, std::size_t byteCount);
|
||||
void Shutdown();
|
||||
|
||||
bool QueueReadback(GLuint framebuffer, unsigned width, unsigned height, uint64_t frameIndex);
|
||||
bool TryAcquireCompleted(CompletedReadback& readback);
|
||||
void ReleaseCompleted(const CompletedReadback& readback);
|
||||
void DrainCompleted();
|
||||
|
||||
std::size_t Depth() const { return mSlots.size(); }
|
||||
uint64_t QueueMisses() const { return mQueueMisses; }
|
||||
|
||||
private:
|
||||
struct Slot
|
||||
{
|
||||
GLuint pbo = 0;
|
||||
GLsync fence = nullptr;
|
||||
bool inFlight = false;
|
||||
bool acquired = false;
|
||||
uint64_t frameIndex = 0;
|
||||
};
|
||||
|
||||
void ResetSlot(Slot& slot);
|
||||
|
||||
std::vector<Slot> mSlots;
|
||||
std::size_t mWriteIndex = 0;
|
||||
std::size_t mReadIndex = 0;
|
||||
std::size_t mByteCount = 0;
|
||||
uint64_t mQueueMisses = 0;
|
||||
};
|
||||
271
src/render/runtime/RuntimeRenderScene.cpp
Normal file
271
src/render/runtime/RuntimeRenderScene.cpp
Normal file
@@ -0,0 +1,271 @@
|
||||
#include "RuntimeRenderScene.h"
|
||||
|
||||
#include "../../platform/HiddenGlWindow.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
|
||||
#ifndef GL_FRAMEBUFFER_BINDING
|
||||
#define GL_FRAMEBUFFER_BINDING 0x8CA6
|
||||
#endif
|
||||
|
||||
RuntimeRenderScene::~RuntimeRenderScene()
|
||||
{
|
||||
ShutdownGl();
|
||||
}
|
||||
|
||||
bool RuntimeRenderScene::StartPrepareWorker(std::unique_ptr<HiddenGlWindow> sharedWindow, std::string& error)
|
||||
{
|
||||
return mPrepareWorker.Start(std::move(sharedWindow), error);
|
||||
}
|
||||
|
||||
bool RuntimeRenderScene::CommitRenderLayers(const std::vector<RenderCadenceCompositor::RuntimeRenderLayerModel>& layers, std::string& error)
|
||||
{
|
||||
ConsumePreparedPrograms();
|
||||
|
||||
std::vector<std::string> nextOrder;
|
||||
std::vector<RenderCadenceCompositor::RuntimeRenderLayerModel> layersToPrepare;
|
||||
nextOrder.reserve(layers.size());
|
||||
for (const RenderCadenceCompositor::RuntimeRenderLayerModel& layer : layers)
|
||||
{
|
||||
if (!layer.bypass)
|
||||
nextOrder.push_back(layer.id);
|
||||
}
|
||||
|
||||
for (auto layerIt = mLayers.begin(); layerIt != mLayers.end();)
|
||||
{
|
||||
const bool stillPresent = std::find(nextOrder.begin(), nextOrder.end(), layerIt->layerId) != nextOrder.end();
|
||||
if (stillPresent)
|
||||
{
|
||||
++layerIt;
|
||||
continue;
|
||||
}
|
||||
|
||||
for (LayerProgram::PassProgram& pass : layerIt->passes)
|
||||
{
|
||||
if (pass.renderer)
|
||||
pass.renderer->ShutdownGl();
|
||||
}
|
||||
ReleasePendingPrograms(*layerIt);
|
||||
layerIt = mLayers.erase(layerIt);
|
||||
}
|
||||
|
||||
for (const RenderCadenceCompositor::RuntimeRenderLayerModel& layer : layers)
|
||||
{
|
||||
if (layer.bypass)
|
||||
continue;
|
||||
if (layer.artifact.passes.empty() && layer.artifact.fragmentShaderSource.empty())
|
||||
continue;
|
||||
|
||||
const std::string fingerprint = Fingerprint(layer.artifact);
|
||||
LayerProgram* program = FindLayer(layer.id);
|
||||
if (!program)
|
||||
{
|
||||
LayerProgram next;
|
||||
next.layerId = layer.id;
|
||||
mLayers.push_back(std::move(next));
|
||||
program = &mLayers.back();
|
||||
}
|
||||
|
||||
bool hasReadyPass = false;
|
||||
for (const LayerProgram::PassProgram& pass : program->passes)
|
||||
{
|
||||
if (pass.renderer && pass.renderer->HasProgram())
|
||||
{
|
||||
hasReadyPass = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (program->shaderId == layer.shaderId && program->sourceFingerprint == fingerprint && hasReadyPass)
|
||||
{
|
||||
for (LayerProgram::PassProgram& pass : program->passes)
|
||||
{
|
||||
if (pass.renderer)
|
||||
pass.renderer->UpdateArtifactState(layer.artifact);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (program->pendingFingerprint == fingerprint)
|
||||
continue;
|
||||
|
||||
ReleasePendingPrograms(*program);
|
||||
program->shaderId = layer.shaderId;
|
||||
program->pendingFingerprint = fingerprint;
|
||||
layersToPrepare.push_back(layer);
|
||||
}
|
||||
|
||||
mLayerOrder = std::move(nextOrder);
|
||||
if (!layersToPrepare.empty())
|
||||
mPrepareWorker.Submit(layersToPrepare);
|
||||
error.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
void RuntimeRenderScene::ShutdownGl()
|
||||
{
|
||||
mPrepareWorker.Stop();
|
||||
for (LayerProgram& layer : mLayers)
|
||||
{
|
||||
for (LayerProgram::PassProgram& pass : layer.passes)
|
||||
{
|
||||
if (pass.renderer)
|
||||
pass.renderer->ShutdownGl();
|
||||
}
|
||||
ReleasePendingPrograms(layer);
|
||||
}
|
||||
mLayers.clear();
|
||||
mLayerOrder.clear();
|
||||
DestroyLayerTargets();
|
||||
}
|
||||
|
||||
void RuntimeRenderScene::ConsumePreparedPrograms()
|
||||
{
|
||||
RuntimePreparedShaderProgram preparedProgram;
|
||||
while (mPrepareWorker.TryConsume(preparedProgram))
|
||||
{
|
||||
if (!preparedProgram.succeeded)
|
||||
{
|
||||
preparedProgram.ReleaseGl();
|
||||
continue;
|
||||
}
|
||||
|
||||
LayerProgram* layer = FindLayer(preparedProgram.layerId);
|
||||
if (!layer || layer->pendingFingerprint != preparedProgram.sourceFingerprint)
|
||||
{
|
||||
preparedProgram.ReleaseGl();
|
||||
continue;
|
||||
}
|
||||
|
||||
bool replacesExistingPendingPass = false;
|
||||
for (RuntimePreparedShaderProgram& existing : layer->pendingPreparedPrograms)
|
||||
{
|
||||
if (existing.passId != preparedProgram.passId)
|
||||
continue;
|
||||
existing.ReleaseGl();
|
||||
existing = std::move(preparedProgram);
|
||||
replacesExistingPendingPass = true;
|
||||
break;
|
||||
}
|
||||
if (!replacesExistingPendingPass)
|
||||
layer->pendingPreparedPrograms.push_back(std::move(preparedProgram));
|
||||
TryCommitPendingPrograms(*layer);
|
||||
}
|
||||
}
|
||||
|
||||
void RuntimeRenderScene::ReleasePendingPrograms(LayerProgram& layer)
|
||||
{
|
||||
for (RuntimePreparedShaderProgram& program : layer.pendingPreparedPrograms)
|
||||
program.ReleaseGl();
|
||||
layer.pendingPreparedPrograms.clear();
|
||||
}
|
||||
|
||||
void RuntimeRenderScene::TryCommitPendingPrograms(LayerProgram& layer)
|
||||
{
|
||||
if (layer.pendingPreparedPrograms.empty())
|
||||
return;
|
||||
|
||||
const RuntimeShaderArtifact& artifact = layer.pendingPreparedPrograms.front().artifact;
|
||||
const std::size_t expectedPassCount = artifact.passes.empty() ? 1 : artifact.passes.size();
|
||||
if (layer.pendingPreparedPrograms.size() < expectedPassCount)
|
||||
return;
|
||||
|
||||
std::vector<LayerProgram::PassProgram> nextPasses;
|
||||
nextPasses.reserve(expectedPassCount);
|
||||
for (const RuntimeShaderPassArtifact& passArtifact : artifact.passes)
|
||||
{
|
||||
auto preparedIt = std::find_if(
|
||||
layer.pendingPreparedPrograms.begin(),
|
||||
layer.pendingPreparedPrograms.end(),
|
||||
[&passArtifact](const RuntimePreparedShaderProgram& prepared) {
|
||||
return prepared.passId == passArtifact.passId;
|
||||
});
|
||||
if (preparedIt == layer.pendingPreparedPrograms.end())
|
||||
return;
|
||||
|
||||
std::unique_ptr<RuntimeShaderRenderer> nextRenderer = std::make_unique<RuntimeShaderRenderer>();
|
||||
std::string error;
|
||||
if (!nextRenderer->CommitPreparedProgram(*preparedIt, error))
|
||||
{
|
||||
ReleasePendingPrograms(layer);
|
||||
return;
|
||||
}
|
||||
|
||||
LayerProgram::PassProgram nextPass;
|
||||
nextPass.passId = preparedIt->passId;
|
||||
nextPass.inputNames = preparedIt->inputNames;
|
||||
nextPass.outputName = preparedIt->outputName.empty() ? preparedIt->passId : preparedIt->outputName;
|
||||
nextPass.renderer = std::move(nextRenderer);
|
||||
nextPasses.push_back(std::move(nextPass));
|
||||
}
|
||||
if (artifact.passes.empty())
|
||||
{
|
||||
RuntimePreparedShaderProgram& prepared = layer.pendingPreparedPrograms.front();
|
||||
std::unique_ptr<RuntimeShaderRenderer> nextRenderer = std::make_unique<RuntimeShaderRenderer>();
|
||||
std::string error;
|
||||
if (!nextRenderer->CommitPreparedProgram(prepared, error))
|
||||
{
|
||||
ReleasePendingPrograms(layer);
|
||||
return;
|
||||
}
|
||||
|
||||
LayerProgram::PassProgram nextPass;
|
||||
nextPass.passId = prepared.passId;
|
||||
nextPass.inputNames = prepared.inputNames;
|
||||
nextPass.outputName = prepared.outputName.empty() ? prepared.passId : prepared.outputName;
|
||||
nextPass.renderer = std::move(nextRenderer);
|
||||
nextPasses.push_back(std::move(nextPass));
|
||||
}
|
||||
|
||||
for (LayerProgram::PassProgram& pass : layer.passes)
|
||||
{
|
||||
if (pass.renderer)
|
||||
pass.renderer->ShutdownGl();
|
||||
}
|
||||
layer.passes = std::move(nextPasses);
|
||||
layer.shaderId = artifact.shaderId;
|
||||
layer.sourceFingerprint = layer.pendingPreparedPrograms.front().sourceFingerprint;
|
||||
layer.pendingFingerprint.clear();
|
||||
layer.pendingPreparedPrograms.clear();
|
||||
}
|
||||
|
||||
RuntimeRenderScene::LayerProgram* RuntimeRenderScene::FindLayer(const std::string& layerId)
|
||||
{
|
||||
for (LayerProgram& layer : mLayers)
|
||||
{
|
||||
if (layer.layerId == layerId)
|
||||
return &layer;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const RuntimeRenderScene::LayerProgram* RuntimeRenderScene::FindLayer(const std::string& layerId) const
|
||||
{
|
||||
for (const LayerProgram& layer : mLayers)
|
||||
{
|
||||
if (layer.layerId == layerId)
|
||||
return &layer;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RuntimeRenderScene::LayerProgram::PassProgram* RuntimeRenderScene::FindPass(LayerProgram& layer, const std::string& passId)
|
||||
{
|
||||
for (LayerProgram::PassProgram& pass : layer.passes)
|
||||
{
|
||||
if (pass.passId == passId)
|
||||
return &pass;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string RuntimeRenderScene::Fingerprint(const RuntimeShaderArtifact& artifact)
|
||||
{
|
||||
const std::hash<std::string> hasher;
|
||||
std::string source;
|
||||
for (const RuntimeShaderPassArtifact& pass : artifact.passes)
|
||||
source += pass.passId + ":" + pass.outputName + ":" + pass.fragmentShaderSource + "\n";
|
||||
if (source.empty())
|
||||
source = artifact.fragmentShaderSource;
|
||||
return artifact.shaderId + ":" + std::to_string(source.size()) + ":" + std::to_string(hasher(source));
|
||||
}
|
||||
64
src/render/runtime/RuntimeRenderScene.h
Normal file
64
src/render/runtime/RuntimeRenderScene.h
Normal file
@@ -0,0 +1,64 @@
|
||||
#pragma once
|
||||
|
||||
#include "../runtime/RuntimeLayerModel.h"
|
||||
#include "RuntimeShaderPrepareWorker.h"
|
||||
#include "RuntimeShaderRenderer.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class RuntimeRenderScene
|
||||
{
|
||||
public:
|
||||
RuntimeRenderScene() = default;
|
||||
RuntimeRenderScene(const RuntimeRenderScene&) = delete;
|
||||
RuntimeRenderScene& operator=(const RuntimeRenderScene&) = delete;
|
||||
~RuntimeRenderScene();
|
||||
|
||||
bool StartPrepareWorker(std::unique_ptr<HiddenGlWindow> sharedWindow, std::string& error);
|
||||
bool CommitRenderLayers(const std::vector<RenderCadenceCompositor::RuntimeRenderLayerModel>& layers, std::string& error);
|
||||
bool HasLayers();
|
||||
void RenderFrame(uint64_t frameIndex, unsigned width, unsigned height, GLuint videoInputTexture = 0);
|
||||
void ShutdownGl();
|
||||
|
||||
private:
|
||||
struct LayerProgram
|
||||
{
|
||||
std::string layerId;
|
||||
std::string shaderId;
|
||||
std::string sourceFingerprint;
|
||||
std::string pendingFingerprint;
|
||||
std::vector<RuntimePreparedShaderProgram> pendingPreparedPrograms;
|
||||
struct PassProgram
|
||||
{
|
||||
std::string passId;
|
||||
std::vector<std::string> inputNames;
|
||||
std::string outputName;
|
||||
std::unique_ptr<RuntimeShaderRenderer> renderer;
|
||||
};
|
||||
std::vector<PassProgram> passes;
|
||||
};
|
||||
|
||||
void ConsumePreparedPrograms();
|
||||
void ReleasePendingPrograms(LayerProgram& layer);
|
||||
void TryCommitPendingPrograms(LayerProgram& layer);
|
||||
bool EnsureLayerTargets(unsigned width, unsigned height);
|
||||
void DestroyLayerTargets();
|
||||
GLuint RenderLayer(LayerProgram& layer, uint64_t frameIndex, unsigned width, unsigned height, GLuint videoInputTexture, GLuint layerInputTexture, GLuint outputFramebuffer, bool renderToOutput);
|
||||
LayerProgram* FindLayer(const std::string& layerId);
|
||||
const LayerProgram* FindLayer(const std::string& layerId) const;
|
||||
LayerProgram::PassProgram* FindPass(LayerProgram& layer, const std::string& passId);
|
||||
static std::string Fingerprint(const RuntimeShaderArtifact& artifact);
|
||||
|
||||
RuntimeShaderPrepareWorker mPrepareWorker;
|
||||
std::vector<LayerProgram> mLayers;
|
||||
std::vector<std::string> mLayerOrder;
|
||||
GLuint mLayerFramebuffers[4] = {};
|
||||
GLuint mLayerTextures[4] = {};
|
||||
unsigned mLayerTargetWidth = 0;
|
||||
unsigned mLayerTargetHeight = 0;
|
||||
};
|
||||
219
src/render/runtime/RuntimeRenderSceneRender.cpp
Normal file
219
src/render/runtime/RuntimeRenderSceneRender.cpp
Normal file
@@ -0,0 +1,219 @@
|
||||
#include "RuntimeRenderScene.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#ifndef GL_FRAMEBUFFER_BINDING
|
||||
#define GL_FRAMEBUFFER_BINDING 0x8CA6
|
||||
#endif
|
||||
|
||||
bool RuntimeRenderScene::HasLayers()
|
||||
{
|
||||
ConsumePreparedPrograms();
|
||||
|
||||
for (const std::string& layerId : mLayerOrder)
|
||||
{
|
||||
const LayerProgram* layer = FindLayer(layerId);
|
||||
if (!layer)
|
||||
continue;
|
||||
for (const LayerProgram::PassProgram& pass : layer->passes)
|
||||
{
|
||||
if (pass.renderer && pass.renderer->HasProgram())
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void RuntimeRenderScene::RenderFrame(uint64_t frameIndex, unsigned width, unsigned height, GLuint videoInputTexture)
|
||||
{
|
||||
ConsumePreparedPrograms();
|
||||
|
||||
std::vector<LayerProgram*> readyLayers;
|
||||
for (const std::string& layerId : mLayerOrder)
|
||||
{
|
||||
LayerProgram* layer = FindLayer(layerId);
|
||||
if (!layer)
|
||||
continue;
|
||||
for (const LayerProgram::PassProgram& pass : layer->passes)
|
||||
{
|
||||
if (pass.renderer && pass.renderer->HasProgram())
|
||||
{
|
||||
readyLayers.push_back(layer);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (readyLayers.empty())
|
||||
return;
|
||||
|
||||
GLint outputFramebuffer = 0;
|
||||
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &outputFramebuffer);
|
||||
|
||||
if (readyLayers.size() == 1)
|
||||
{
|
||||
RenderLayer(*readyLayers.front(), frameIndex, width, height, videoInputTexture, videoInputTexture, static_cast<GLuint>(outputFramebuffer), true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EnsureLayerTargets(width, height))
|
||||
{
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, static_cast<GLuint>(outputFramebuffer));
|
||||
RenderLayer(*readyLayers.back(), frameIndex, width, height, videoInputTexture, videoInputTexture, static_cast<GLuint>(outputFramebuffer), true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Shader source contract:
|
||||
// - gVideoInput is the decoded current input texture for every layer in the stack.
|
||||
// - gLayerInput starts as gVideoInput for the first layer, then becomes the previous layer output.
|
||||
GLuint layerInputTexture = videoInputTexture;
|
||||
std::size_t nextTargetIndex = 0;
|
||||
for (std::size_t layerIndex = 0; layerIndex < readyLayers.size(); ++layerIndex)
|
||||
{
|
||||
const bool isFinalLayer = layerIndex == readyLayers.size() - 1;
|
||||
if (isFinalLayer)
|
||||
{
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, static_cast<GLuint>(outputFramebuffer));
|
||||
RenderLayer(*readyLayers[layerIndex], frameIndex, width, height, videoInputTexture, layerInputTexture, static_cast<GLuint>(outputFramebuffer), true);
|
||||
continue;
|
||||
}
|
||||
|
||||
RenderLayer(*readyLayers[layerIndex], frameIndex, width, height, videoInputTexture, layerInputTexture, mLayerFramebuffers[nextTargetIndex], true);
|
||||
layerInputTexture = mLayerTextures[nextTargetIndex];
|
||||
nextTargetIndex = 1 - nextTargetIndex;
|
||||
}
|
||||
}
|
||||
|
||||
GLuint RuntimeRenderScene::RenderLayer(
|
||||
LayerProgram& layer,
|
||||
uint64_t frameIndex,
|
||||
unsigned width,
|
||||
unsigned height,
|
||||
GLuint videoInputTexture,
|
||||
GLuint layerInputTexture,
|
||||
GLuint outputFramebuffer,
|
||||
bool renderToOutput)
|
||||
{
|
||||
GLuint namedOutputs[2] = {};
|
||||
std::string namedOutputNames[2];
|
||||
std::size_t nextTargetIndex = 2;
|
||||
GLuint lastOutputTexture = layerInputTexture;
|
||||
|
||||
for (LayerProgram::PassProgram& pass : layer.passes)
|
||||
{
|
||||
if (!pass.renderer || !pass.renderer->HasProgram())
|
||||
continue;
|
||||
|
||||
GLuint sourceTexture = videoInputTexture;
|
||||
if (!pass.inputNames.empty())
|
||||
{
|
||||
const std::string& inputName = pass.inputNames.front();
|
||||
if (inputName == "videoInput")
|
||||
{
|
||||
sourceTexture = videoInputTexture;
|
||||
}
|
||||
else if (inputName != "layerInput")
|
||||
{
|
||||
// Named intermediate pass inputs currently use the gVideoInput binding slot as the
|
||||
// selected pass source. Layer stack shaders should use gLayerInput for previous-layer
|
||||
// sampling and gVideoInput for the original input frame.
|
||||
for (std::size_t index = 0; index < 2; ++index)
|
||||
{
|
||||
if (namedOutputNames[index] == inputName)
|
||||
{
|
||||
sourceTexture = namedOutputs[index];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const bool writesLayerOutput = pass.outputName == "layerOutput";
|
||||
if (writesLayerOutput && renderToOutput)
|
||||
{
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, outputFramebuffer);
|
||||
pass.renderer->RenderFrame(frameIndex, width, height, sourceTexture, layerInputTexture);
|
||||
lastOutputTexture = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!EnsureLayerTargets(width, height))
|
||||
continue;
|
||||
|
||||
const std::size_t targetIndex = nextTargetIndex;
|
||||
nextTargetIndex = nextTargetIndex == 2 ? 3 : 2;
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, mLayerFramebuffers[targetIndex]);
|
||||
pass.renderer->RenderFrame(frameIndex, width, height, sourceTexture, layerInputTexture);
|
||||
const std::size_t namedIndex = targetIndex - 2;
|
||||
namedOutputs[namedIndex] = mLayerTextures[targetIndex];
|
||||
namedOutputNames[namedIndex] = pass.outputName;
|
||||
lastOutputTexture = mLayerTextures[targetIndex];
|
||||
}
|
||||
|
||||
return lastOutputTexture;
|
||||
}
|
||||
|
||||
bool RuntimeRenderScene::EnsureLayerTargets(unsigned width, unsigned height)
|
||||
{
|
||||
if (width == 0 || height == 0)
|
||||
return false;
|
||||
if (mLayerFramebuffers[0] != 0 && mLayerFramebuffers[1] != 0 && mLayerFramebuffers[2] != 0 && mLayerFramebuffers[3] != 0
|
||||
&& mLayerTextures[0] != 0 && mLayerTextures[1] != 0 && mLayerTextures[2] != 0 && mLayerTextures[3] != 0
|
||||
&& mLayerTargetWidth == width && mLayerTargetHeight == height)
|
||||
return true;
|
||||
|
||||
DestroyLayerTargets();
|
||||
mLayerTargetWidth = width;
|
||||
mLayerTargetHeight = height;
|
||||
|
||||
glGenFramebuffers(4, mLayerFramebuffers);
|
||||
glGenTextures(4, mLayerTextures);
|
||||
for (int index = 0; index < 4; ++index)
|
||||
{
|
||||
glBindTexture(GL_TEXTURE_2D, mLayerTextures[index]);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexImage2D(
|
||||
GL_TEXTURE_2D,
|
||||
0,
|
||||
GL_RGBA8,
|
||||
static_cast<GLsizei>(width),
|
||||
static_cast<GLsizei>(height),
|
||||
0,
|
||||
GL_BGRA,
|
||||
GL_UNSIGNED_INT_8_8_8_8_REV,
|
||||
nullptr);
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, mLayerFramebuffers[index]);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mLayerTextures[index], 0);
|
||||
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
|
||||
{
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
DestroyLayerTargets();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
void RuntimeRenderScene::DestroyLayerTargets()
|
||||
{
|
||||
if (mLayerFramebuffers[0] != 0 || mLayerFramebuffers[1] != 0 || mLayerFramebuffers[2] != 0 || mLayerFramebuffers[3] != 0)
|
||||
glDeleteFramebuffers(4, mLayerFramebuffers);
|
||||
if (mLayerTextures[0] != 0 || mLayerTextures[1] != 0 || mLayerTextures[2] != 0 || mLayerTextures[3] != 0)
|
||||
glDeleteTextures(4, mLayerTextures);
|
||||
for (int index = 0; index < 4; ++index)
|
||||
{
|
||||
mLayerFramebuffers[index] = 0;
|
||||
mLayerTextures[index] = 0;
|
||||
}
|
||||
mLayerTargetWidth = 0;
|
||||
mLayerTargetHeight = 0;
|
||||
}
|
||||
123
src/render/runtime/RuntimeShaderParams.cpp
Normal file
123
src/render/runtime/RuntimeShaderParams.cpp
Normal file
@@ -0,0 +1,123 @@
|
||||
#include "RuntimeShaderParams.h"
|
||||
|
||||
#include "Std140Buffer.h"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace
|
||||
{
|
||||
ShaderParameterValue DefaultValueForDefinition(const ShaderParameterDefinition& definition)
|
||||
{
|
||||
ShaderParameterValue value;
|
||||
switch (definition.type)
|
||||
{
|
||||
case ShaderParameterType::Float:
|
||||
value.numberValues = definition.defaultNumbers.empty() ? std::vector<double>{ 0.0 } : definition.defaultNumbers;
|
||||
break;
|
||||
case ShaderParameterType::Vec2:
|
||||
value.numberValues = definition.defaultNumbers.size() == 2 ? definition.defaultNumbers : std::vector<double>{ 0.0, 0.0 };
|
||||
break;
|
||||
case ShaderParameterType::Color:
|
||||
value.numberValues = definition.defaultNumbers.size() == 4 ? definition.defaultNumbers : std::vector<double>{ 1.0, 1.0, 1.0, 1.0 };
|
||||
break;
|
||||
case ShaderParameterType::Boolean:
|
||||
value.booleanValue = definition.defaultBoolean;
|
||||
break;
|
||||
case ShaderParameterType::Enum:
|
||||
value.enumValue = definition.defaultEnumValue;
|
||||
break;
|
||||
case ShaderParameterType::Text:
|
||||
value.textValue = definition.defaultTextValue;
|
||||
break;
|
||||
case ShaderParameterType::Trigger:
|
||||
value.numberValues = { 0.0, -1000000.0 };
|
||||
break;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
int EnumIndexForDefault(const ShaderParameterDefinition& definition, const ShaderParameterValue& value)
|
||||
{
|
||||
for (std::size_t optionIndex = 0; optionIndex < definition.enumOptions.size(); ++optionIndex)
|
||||
{
|
||||
if (definition.enumOptions[optionIndex].value == value.enumValue)
|
||||
return static_cast<int>(optionIndex);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
double UtcSecondsOfDay()
|
||||
{
|
||||
const auto now = std::chrono::system_clock::now();
|
||||
const auto seconds = std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
|
||||
const long long secondsPerDay = 24 * 60 * 60;
|
||||
long long daySeconds = seconds % secondsPerDay;
|
||||
if (daySeconds < 0)
|
||||
daySeconds += secondsPerDay;
|
||||
return static_cast<double>(daySeconds);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<unsigned char> BuildRuntimeShaderGlobalParamsStd140(
|
||||
const RuntimeShaderArtifact& artifact,
|
||||
uint64_t frameIndex,
|
||||
unsigned width,
|
||||
unsigned height)
|
||||
{
|
||||
std::vector<unsigned char> buffer;
|
||||
buffer.reserve(512);
|
||||
|
||||
AppendStd140Float(buffer, static_cast<float>(frameIndex) / 60.0f);
|
||||
AppendStd140Vec2(buffer, static_cast<float>(width), static_cast<float>(height));
|
||||
AppendStd140Vec2(buffer, static_cast<float>(width), static_cast<float>(height));
|
||||
AppendStd140Float(buffer, static_cast<float>(UtcSecondsOfDay()));
|
||||
AppendStd140Float(buffer, 0.0f);
|
||||
AppendStd140Float(buffer, 0.37f);
|
||||
AppendStd140Float(buffer, static_cast<float>(frameIndex));
|
||||
AppendStd140Float(buffer, 1.0f);
|
||||
AppendStd140Float(buffer, 0.0f);
|
||||
AppendStd140Int(buffer, 0);
|
||||
AppendStd140Int(buffer, 0);
|
||||
AppendStd140Int(buffer, 0);
|
||||
|
||||
for (const ShaderParameterDefinition& definition : artifact.parameterDefinitions)
|
||||
{
|
||||
const auto valueIt = artifact.parameterValues.find(definition.id);
|
||||
const ShaderParameterValue value = valueIt == artifact.parameterValues.end()
|
||||
? DefaultValueForDefinition(definition)
|
||||
: valueIt->second;
|
||||
switch (definition.type)
|
||||
{
|
||||
case ShaderParameterType::Float:
|
||||
AppendStd140Float(buffer, value.numberValues.empty() ? 0.0f : static_cast<float>(value.numberValues[0]));
|
||||
break;
|
||||
case ShaderParameterType::Vec2:
|
||||
AppendStd140Vec2(buffer,
|
||||
value.numberValues.size() > 0 ? static_cast<float>(value.numberValues[0]) : 0.0f,
|
||||
value.numberValues.size() > 1 ? static_cast<float>(value.numberValues[1]) : 0.0f);
|
||||
break;
|
||||
case ShaderParameterType::Color:
|
||||
AppendStd140Vec4(buffer,
|
||||
value.numberValues.size() > 0 ? static_cast<float>(value.numberValues[0]) : 1.0f,
|
||||
value.numberValues.size() > 1 ? static_cast<float>(value.numberValues[1]) : 1.0f,
|
||||
value.numberValues.size() > 2 ? static_cast<float>(value.numberValues[2]) : 1.0f,
|
||||
value.numberValues.size() > 3 ? static_cast<float>(value.numberValues[3]) : 1.0f);
|
||||
break;
|
||||
case ShaderParameterType::Boolean:
|
||||
AppendStd140Int(buffer, value.booleanValue ? 1 : 0);
|
||||
break;
|
||||
case ShaderParameterType::Enum:
|
||||
AppendStd140Int(buffer, EnumIndexForDefault(definition, value));
|
||||
break;
|
||||
case ShaderParameterType::Text:
|
||||
break;
|
||||
case ShaderParameterType::Trigger:
|
||||
AppendStd140Int(buffer, value.numberValues.empty() ? 0 : static_cast<int>(value.numberValues[0]));
|
||||
AppendStd140Float(buffer, value.numberValues.size() > 1 ? static_cast<float>(value.numberValues[1]) : -1000000.0f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
buffer.resize(AlignStd140(buffer.size(), 16), 0);
|
||||
return buffer;
|
||||
}
|
||||
12
src/render/runtime/RuntimeShaderParams.h
Normal file
12
src/render/runtime/RuntimeShaderParams.h
Normal file
@@ -0,0 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "../runtime/RuntimeShaderArtifact.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
std::vector<unsigned char> BuildRuntimeShaderGlobalParamsStd140(
|
||||
const RuntimeShaderArtifact& artifact,
|
||||
uint64_t frameIndex,
|
||||
unsigned width,
|
||||
unsigned height);
|
||||
179
src/render/runtime/RuntimeShaderPrepareWorker.cpp
Normal file
179
src/render/runtime/RuntimeShaderPrepareWorker.cpp
Normal file
@@ -0,0 +1,179 @@
|
||||
#include "RuntimeShaderPrepareWorker.h"
|
||||
|
||||
#include "../../platform/HiddenGlWindow.h"
|
||||
#include "RuntimeShaderRenderer.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <utility>
|
||||
|
||||
RuntimeShaderPrepareWorker::~RuntimeShaderPrepareWorker()
|
||||
{
|
||||
Stop();
|
||||
}
|
||||
|
||||
bool RuntimeShaderPrepareWorker::Start(std::unique_ptr<HiddenGlWindow> sharedWindow, std::string& error)
|
||||
{
|
||||
if (mThread.joinable())
|
||||
return true;
|
||||
if (!sharedWindow || sharedWindow->DeviceContext() == nullptr || sharedWindow->Context() == nullptr)
|
||||
{
|
||||
error = "Runtime shader prepare worker needs an existing shared GL context.";
|
||||
return false;
|
||||
}
|
||||
|
||||
mWindow = std::move(sharedWindow);
|
||||
mStopping.store(false, std::memory_order_release);
|
||||
mStarted.store(false, std::memory_order_release);
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mStartupReady = false;
|
||||
mStartupError.clear();
|
||||
}
|
||||
mThread = std::thread([this]() { ThreadMain(); });
|
||||
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
if (!mStartupCondition.wait_for(lock, std::chrono::seconds(3), [this]() {
|
||||
return mStartupReady || !mStartupError.empty();
|
||||
}))
|
||||
{
|
||||
error = "Timed out starting runtime shader prepare worker.";
|
||||
lock.unlock();
|
||||
Stop();
|
||||
return false;
|
||||
}
|
||||
if (!mStartupError.empty())
|
||||
{
|
||||
error = mStartupError;
|
||||
lock.unlock();
|
||||
Stop();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void RuntimeShaderPrepareWorker::Stop()
|
||||
{
|
||||
mStopping.store(true, std::memory_order_release);
|
||||
mCondition.notify_all();
|
||||
if (mThread.joinable())
|
||||
mThread.join();
|
||||
|
||||
std::deque<RuntimePreparedShaderProgram> completed;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mRequests.clear();
|
||||
completed.swap(mCompleted);
|
||||
}
|
||||
for (RuntimePreparedShaderProgram& program : completed)
|
||||
program.ReleaseGl();
|
||||
mWindow.reset();
|
||||
mStarted.store(false, std::memory_order_release);
|
||||
}
|
||||
|
||||
void RuntimeShaderPrepareWorker::Submit(const std::vector<RenderCadenceCompositor::RuntimeRenderLayerModel>& layers)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
for (const RenderCadenceCompositor::RuntimeRenderLayerModel& layer : layers)
|
||||
{
|
||||
if (layer.artifact.passes.empty() && layer.artifact.fragmentShaderSource.empty())
|
||||
continue;
|
||||
|
||||
std::vector<RuntimeShaderPassArtifact> passes = layer.artifact.passes;
|
||||
if (passes.empty())
|
||||
{
|
||||
RuntimeShaderPassArtifact pass;
|
||||
pass.passId = "main";
|
||||
pass.fragmentShaderSource = layer.artifact.fragmentShaderSource;
|
||||
pass.outputName = "layerOutput";
|
||||
passes.push_back(std::move(pass));
|
||||
}
|
||||
|
||||
auto sameLayer = [&layer](const PrepareRequest& existing) {
|
||||
return existing.layerId == layer.id;
|
||||
};
|
||||
mRequests.erase(std::remove_if(mRequests.begin(), mRequests.end(), sameLayer), mRequests.end());
|
||||
|
||||
for (const RuntimeShaderPassArtifact& pass : passes)
|
||||
{
|
||||
PrepareRequest request;
|
||||
request.layerId = layer.id;
|
||||
request.shaderId = layer.shaderId;
|
||||
request.passId = pass.passId;
|
||||
request.sourceFingerprint = Fingerprint(layer.artifact);
|
||||
request.artifact = layer.artifact;
|
||||
request.passArtifact = pass;
|
||||
mRequests.push_back(std::move(request));
|
||||
}
|
||||
}
|
||||
mCondition.notify_one();
|
||||
}
|
||||
|
||||
bool RuntimeShaderPrepareWorker::TryConsume(RuntimePreparedShaderProgram& preparedProgram)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
if (mCompleted.empty())
|
||||
return false;
|
||||
|
||||
preparedProgram = std::move(mCompleted.front());
|
||||
mCompleted.pop_front();
|
||||
return true;
|
||||
}
|
||||
|
||||
void RuntimeShaderPrepareWorker::ThreadMain()
|
||||
{
|
||||
if (!mWindow || !mWindow->MakeCurrent())
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mStartupError = "Runtime shader prepare worker could not make shared GL context current.";
|
||||
mStartupCondition.notify_all();
|
||||
return;
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mStartupReady = true;
|
||||
}
|
||||
mStarted.store(true, std::memory_order_release);
|
||||
mStartupCondition.notify_all();
|
||||
|
||||
while (!mStopping.load(std::memory_order_acquire))
|
||||
{
|
||||
PrepareRequest request;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex);
|
||||
mCondition.wait(lock, [this]() {
|
||||
return mStopping.load(std::memory_order_acquire) || !mRequests.empty();
|
||||
});
|
||||
if (mStopping.load(std::memory_order_acquire))
|
||||
break;
|
||||
request = std::move(mRequests.front());
|
||||
mRequests.pop_front();
|
||||
}
|
||||
|
||||
RuntimePreparedShaderProgram preparedProgram;
|
||||
RuntimeShaderRenderer::BuildPreparedPassProgram(
|
||||
request.layerId,
|
||||
request.sourceFingerprint,
|
||||
request.artifact,
|
||||
request.passArtifact,
|
||||
preparedProgram);
|
||||
glFlush();
|
||||
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
mCompleted.push_back(std::move(preparedProgram));
|
||||
}
|
||||
|
||||
mWindow->ClearCurrent();
|
||||
}
|
||||
|
||||
std::string RuntimeShaderPrepareWorker::Fingerprint(const RuntimeShaderArtifact& artifact)
|
||||
{
|
||||
const std::hash<std::string> hasher;
|
||||
std::string source;
|
||||
for (const RuntimeShaderPassArtifact& pass : artifact.passes)
|
||||
source += pass.passId + ":" + pass.outputName + ":" + pass.fragmentShaderSource + "\n";
|
||||
if (source.empty())
|
||||
source = artifact.fragmentShaderSource;
|
||||
return artifact.shaderId + ":" + std::to_string(source.size()) + ":" + std::to_string(hasher(source));
|
||||
}
|
||||
58
src/render/runtime/RuntimeShaderPrepareWorker.h
Normal file
58
src/render/runtime/RuntimeShaderPrepareWorker.h
Normal file
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include "RuntimeShaderProgram.h"
|
||||
#include "../../runtime/RuntimeLayerModel.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
class HiddenGlWindow;
|
||||
|
||||
class RuntimeShaderPrepareWorker
|
||||
{
|
||||
public:
|
||||
RuntimeShaderPrepareWorker() = default;
|
||||
RuntimeShaderPrepareWorker(const RuntimeShaderPrepareWorker&) = delete;
|
||||
RuntimeShaderPrepareWorker& operator=(const RuntimeShaderPrepareWorker&) = delete;
|
||||
~RuntimeShaderPrepareWorker();
|
||||
|
||||
bool Start(std::unique_ptr<HiddenGlWindow> sharedWindow, std::string& error);
|
||||
void Stop();
|
||||
|
||||
void Submit(const std::vector<RenderCadenceCompositor::RuntimeRenderLayerModel>& layers);
|
||||
bool TryConsume(RuntimePreparedShaderProgram& preparedProgram);
|
||||
|
||||
private:
|
||||
struct PrepareRequest
|
||||
{
|
||||
std::string layerId;
|
||||
std::string shaderId;
|
||||
std::string passId;
|
||||
std::string sourceFingerprint;
|
||||
RuntimeShaderArtifact artifact;
|
||||
RuntimeShaderPassArtifact passArtifact;
|
||||
};
|
||||
|
||||
void ThreadMain();
|
||||
static std::string Fingerprint(const RuntimeShaderArtifact& artifact);
|
||||
|
||||
std::unique_ptr<HiddenGlWindow> mWindow;
|
||||
std::mutex mMutex;
|
||||
std::condition_variable mCondition;
|
||||
std::deque<PrepareRequest> mRequests;
|
||||
std::deque<RuntimePreparedShaderProgram> mCompleted;
|
||||
std::condition_variable mStartupCondition;
|
||||
std::thread mThread;
|
||||
std::atomic<bool> mStopping{ false };
|
||||
std::atomic<bool> mStarted{ false };
|
||||
bool mStartupReady = false;
|
||||
std::string mStartupError;
|
||||
};
|
||||
37
src/render/runtime/RuntimeShaderProgram.h
Normal file
37
src/render/runtime/RuntimeShaderProgram.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include "GLExtensions.h"
|
||||
#include "../../runtime/RuntimeShaderArtifact.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct RuntimePreparedShaderProgram
|
||||
{
|
||||
std::string layerId;
|
||||
std::string shaderId;
|
||||
std::string passId;
|
||||
std::string sourceFingerprint;
|
||||
RuntimeShaderArtifact artifact;
|
||||
RuntimeShaderPassArtifact passArtifact;
|
||||
std::vector<std::string> inputNames;
|
||||
std::string outputName;
|
||||
GLuint program = 0;
|
||||
GLuint vertexShader = 0;
|
||||
GLuint fragmentShader = 0;
|
||||
bool succeeded = false;
|
||||
std::string error;
|
||||
|
||||
void ReleaseGl()
|
||||
{
|
||||
if (program != 0)
|
||||
glDeleteProgram(program);
|
||||
if (vertexShader != 0)
|
||||
glDeleteShader(vertexShader);
|
||||
if (fragmentShader != 0)
|
||||
glDeleteShader(fragmentShader);
|
||||
program = 0;
|
||||
vertexShader = 0;
|
||||
fragmentShader = 0;
|
||||
}
|
||||
};
|
||||
320
src/render/runtime/RuntimeShaderRenderer.cpp
Normal file
320
src/render/runtime/RuntimeShaderRenderer.cpp
Normal file
@@ -0,0 +1,320 @@
|
||||
#include "RuntimeShaderRenderer.h"
|
||||
|
||||
#include "RuntimeShaderParams.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr GLuint kGlobalParamsBindingPoint = 0;
|
||||
constexpr GLuint kVideoInputTextureUnit = 0;
|
||||
constexpr GLuint kLayerInputTextureUnit = 1;
|
||||
|
||||
const char* kVertexShaderSource = R"GLSL(
|
||||
#version 430 core
|
||||
out vec2 vTexCoord;
|
||||
void main()
|
||||
{
|
||||
vec2 positions[3] = vec2[3](
|
||||
vec2(-1.0, -1.0),
|
||||
vec2( 3.0, -1.0),
|
||||
vec2(-1.0, 3.0));
|
||||
vec2 texCoords[3] = vec2[3](
|
||||
vec2(0.0, 0.0),
|
||||
vec2(2.0, 0.0),
|
||||
vec2(0.0, 2.0));
|
||||
gl_Position = vec4(positions[gl_VertexID], 0.0, 1.0);
|
||||
vTexCoord = texCoords[gl_VertexID];
|
||||
}
|
||||
)GLSL";
|
||||
|
||||
}
|
||||
|
||||
RuntimeShaderRenderer::~RuntimeShaderRenderer()
|
||||
{
|
||||
ShutdownGl();
|
||||
}
|
||||
|
||||
bool RuntimeShaderRenderer::CommitPreparedProgram(RuntimePreparedShaderProgram& preparedProgram, std::string& error)
|
||||
{
|
||||
if (!preparedProgram.succeeded || preparedProgram.program == 0)
|
||||
{
|
||||
error = preparedProgram.error.empty() ? "Prepared runtime shader program is not valid." : preparedProgram.error;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!EnsureStaticGlResources(error))
|
||||
return false;
|
||||
|
||||
DestroyProgram();
|
||||
mProgram = preparedProgram.program;
|
||||
mVertexShader = preparedProgram.vertexShader;
|
||||
mFragmentShader = preparedProgram.fragmentShader;
|
||||
mArtifact = preparedProgram.artifact;
|
||||
preparedProgram.program = 0;
|
||||
preparedProgram.vertexShader = 0;
|
||||
preparedProgram.fragmentShader = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
void RuntimeShaderRenderer::UpdateArtifactState(const RuntimeShaderArtifact& artifact)
|
||||
{
|
||||
mArtifact.parameterDefinitions = artifact.parameterDefinitions;
|
||||
mArtifact.parameterValues = artifact.parameterValues;
|
||||
mArtifact.message = artifact.message;
|
||||
}
|
||||
|
||||
bool RuntimeShaderRenderer::BuildPreparedProgram(
|
||||
const std::string& layerId,
|
||||
const std::string& sourceFingerprint,
|
||||
const RuntimeShaderArtifact& artifact,
|
||||
RuntimePreparedShaderProgram& preparedProgram)
|
||||
{
|
||||
RuntimeShaderPassArtifact passArtifact;
|
||||
passArtifact.passId = "main";
|
||||
passArtifact.fragmentShaderSource = artifact.fragmentShaderSource;
|
||||
passArtifact.outputName = "layerOutput";
|
||||
if (!artifact.passes.empty())
|
||||
passArtifact = artifact.passes.front();
|
||||
return BuildPreparedPassProgram(layerId, sourceFingerprint, artifact, passArtifact, preparedProgram);
|
||||
}
|
||||
|
||||
bool RuntimeShaderRenderer::BuildPreparedPassProgram(
|
||||
const std::string& layerId,
|
||||
const std::string& sourceFingerprint,
|
||||
const RuntimeShaderArtifact& artifact,
|
||||
const RuntimeShaderPassArtifact& passArtifact,
|
||||
RuntimePreparedShaderProgram& preparedProgram)
|
||||
{
|
||||
preparedProgram = RuntimePreparedShaderProgram();
|
||||
preparedProgram.layerId = layerId;
|
||||
preparedProgram.shaderId = artifact.shaderId;
|
||||
preparedProgram.passId = passArtifact.passId;
|
||||
preparedProgram.sourceFingerprint = sourceFingerprint;
|
||||
preparedProgram.artifact = artifact;
|
||||
preparedProgram.passArtifact = passArtifact;
|
||||
preparedProgram.inputNames = passArtifact.inputNames;
|
||||
preparedProgram.outputName = passArtifact.outputName.empty() ? passArtifact.passId : passArtifact.outputName;
|
||||
|
||||
if (passArtifact.fragmentShaderSource.empty())
|
||||
{
|
||||
preparedProgram.error = "Cannot prepare an empty fragment shader.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!BuildProgram(
|
||||
passArtifact.fragmentShaderSource,
|
||||
preparedProgram.program,
|
||||
preparedProgram.vertexShader,
|
||||
preparedProgram.fragmentShader,
|
||||
preparedProgram.error))
|
||||
{
|
||||
preparedProgram.ReleaseGl();
|
||||
return false;
|
||||
}
|
||||
|
||||
preparedProgram.succeeded = true;
|
||||
AssignSamplerUniforms(preparedProgram.program);
|
||||
return true;
|
||||
}
|
||||
|
||||
void RuntimeShaderRenderer::RenderFrame(uint64_t frameIndex, unsigned width, unsigned height, GLuint sourceTexture, GLuint layerInputTexture)
|
||||
{
|
||||
if (mProgram == 0)
|
||||
return;
|
||||
|
||||
glViewport(0, 0, static_cast<GLsizei>(width), static_cast<GLsizei>(height));
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glDisable(GL_BLEND);
|
||||
UpdateGlobalParams(frameIndex, width, height);
|
||||
BindRuntimeTextures(sourceTexture, layerInputTexture);
|
||||
glBindVertexArray(mVertexArray);
|
||||
glUseProgram(mProgram);
|
||||
glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||
glUseProgram(0);
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
void RuntimeShaderRenderer::ShutdownGl()
|
||||
{
|
||||
DestroyProgram();
|
||||
DestroyStaticGlResources();
|
||||
}
|
||||
|
||||
bool RuntimeShaderRenderer::EnsureStaticGlResources(std::string& error)
|
||||
{
|
||||
if (mVertexArray == 0)
|
||||
glGenVertexArrays(1, &mVertexArray);
|
||||
if (mGlobalParamsBuffer == 0)
|
||||
{
|
||||
glGenBuffers(1, &mGlobalParamsBuffer);
|
||||
glBindBuffer(GL_UNIFORM_BUFFER, mGlobalParamsBuffer);
|
||||
glBufferData(GL_UNIFORM_BUFFER, 1024, nullptr, GL_DYNAMIC_DRAW);
|
||||
glBindBuffer(GL_UNIFORM_BUFFER, 0);
|
||||
}
|
||||
if (mFallbackSourceTexture == 0)
|
||||
{
|
||||
const unsigned char pixels[] = {
|
||||
0, 0, 0, 255,
|
||||
96, 64, 32, 255,
|
||||
64, 96, 160, 255,
|
||||
255, 255, 255, 255
|
||||
};
|
||||
glGenTextures(1, &mFallbackSourceTexture);
|
||||
glBindTexture(GL_TEXTURE_2D, mFallbackSourceTexture);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 2, 2, 0, GL_BGRA, GL_UNSIGNED_BYTE, pixels);
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
if (mVertexArray == 0 || mGlobalParamsBuffer == 0 || mFallbackSourceTexture == 0)
|
||||
{
|
||||
error = "Failed to create runtime shader GL resources.";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RuntimeShaderRenderer::BuildProgram(const std::string& fragmentShaderSource, GLuint& program, GLuint& vertexShader, GLuint& fragmentShader, std::string& error)
|
||||
{
|
||||
program = 0;
|
||||
vertexShader = 0;
|
||||
fragmentShader = 0;
|
||||
|
||||
if (!CompileShader(GL_VERTEX_SHADER, kVertexShaderSource, vertexShader, error))
|
||||
return false;
|
||||
if (!CompileShader(GL_FRAGMENT_SHADER, fragmentShaderSource.c_str(), fragmentShader, error))
|
||||
{
|
||||
glDeleteShader(vertexShader);
|
||||
vertexShader = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
program = glCreateProgram();
|
||||
glAttachShader(program, vertexShader);
|
||||
glAttachShader(program, fragmentShader);
|
||||
glLinkProgram(program);
|
||||
|
||||
GLint linkResult = GL_FALSE;
|
||||
glGetProgramiv(program, GL_LINK_STATUS, &linkResult);
|
||||
if (linkResult == GL_FALSE)
|
||||
{
|
||||
std::array<char, 4096> log = {};
|
||||
GLsizei length = 0;
|
||||
glGetProgramInfoLog(program, static_cast<GLsizei>(log.size()), &length, log.data());
|
||||
error = std::string(log.data(), static_cast<std::size_t>(length));
|
||||
glDeleteProgram(program);
|
||||
glDeleteShader(vertexShader);
|
||||
glDeleteShader(fragmentShader);
|
||||
program = 0;
|
||||
vertexShader = 0;
|
||||
fragmentShader = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
const GLuint globalParamsIndex = glGetUniformBlockIndex(program, "GlobalParams");
|
||||
if (globalParamsIndex != GL_INVALID_INDEX)
|
||||
glUniformBlockBinding(program, globalParamsIndex, kGlobalParamsBindingPoint);
|
||||
return true;
|
||||
}
|
||||
|
||||
void RuntimeShaderRenderer::AssignSamplerUniforms(GLuint program)
|
||||
{
|
||||
glUseProgram(program);
|
||||
const GLint videoInputLocation = glGetUniformLocation(program, "gVideoInput");
|
||||
if (videoInputLocation >= 0)
|
||||
glUniform1i(videoInputLocation, static_cast<GLint>(kVideoInputTextureUnit));
|
||||
const GLint videoInputArrayLocation = glGetUniformLocation(program, "gVideoInput_0");
|
||||
if (videoInputArrayLocation >= 0)
|
||||
glUniform1i(videoInputArrayLocation, static_cast<GLint>(kVideoInputTextureUnit));
|
||||
const GLint layerInputLocation = glGetUniformLocation(program, "gLayerInput");
|
||||
if (layerInputLocation >= 0)
|
||||
glUniform1i(layerInputLocation, static_cast<GLint>(kLayerInputTextureUnit));
|
||||
const GLint layerInputArrayLocation = glGetUniformLocation(program, "gLayerInput_0");
|
||||
if (layerInputArrayLocation >= 0)
|
||||
glUniform1i(layerInputArrayLocation, static_cast<GLint>(kLayerInputTextureUnit));
|
||||
glUseProgram(0);
|
||||
}
|
||||
|
||||
void RuntimeShaderRenderer::UpdateGlobalParams(uint64_t frameIndex, unsigned width, unsigned height)
|
||||
{
|
||||
std::vector<unsigned char>& buffer = mGlobalParamsScratch;
|
||||
buffer = BuildRuntimeShaderGlobalParamsStd140(mArtifact, frameIndex, width, height);
|
||||
glBindBuffer(GL_UNIFORM_BUFFER, mGlobalParamsBuffer);
|
||||
const GLsizeiptr bufferSize = static_cast<GLsizeiptr>(buffer.size());
|
||||
if (mGlobalParamsBufferSize != bufferSize)
|
||||
{
|
||||
glBufferData(GL_UNIFORM_BUFFER, bufferSize, buffer.data(), GL_DYNAMIC_DRAW);
|
||||
mGlobalParamsBufferSize = bufferSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
glBufferSubData(GL_UNIFORM_BUFFER, 0, bufferSize, buffer.data());
|
||||
}
|
||||
glBindBufferBase(GL_UNIFORM_BUFFER, kGlobalParamsBindingPoint, mGlobalParamsBuffer);
|
||||
glBindBuffer(GL_UNIFORM_BUFFER, 0);
|
||||
}
|
||||
|
||||
void RuntimeShaderRenderer::BindRuntimeTextures(GLuint sourceTexture, GLuint layerInputTexture)
|
||||
{
|
||||
const GLuint resolvedSourceTexture = sourceTexture != 0 ? sourceTexture : mFallbackSourceTexture;
|
||||
const GLuint resolvedLayerInputTexture = layerInputTexture != 0 ? layerInputTexture : resolvedSourceTexture;
|
||||
glActiveTexture(GL_TEXTURE0 + kVideoInputTextureUnit);
|
||||
glBindTexture(GL_TEXTURE_2D, resolvedSourceTexture);
|
||||
glActiveTexture(GL_TEXTURE0 + kLayerInputTextureUnit);
|
||||
glBindTexture(GL_TEXTURE_2D, resolvedLayerInputTexture);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
}
|
||||
|
||||
bool RuntimeShaderRenderer::CompileShader(GLenum shaderType, const char* source, GLuint& shader, std::string& error)
|
||||
{
|
||||
shader = glCreateShader(shaderType);
|
||||
glShaderSource(shader, 1, &source, nullptr);
|
||||
glCompileShader(shader);
|
||||
|
||||
GLint compileResult = GL_FALSE;
|
||||
glGetShaderiv(shader, GL_COMPILE_STATUS, &compileResult);
|
||||
if (compileResult != GL_FALSE)
|
||||
return true;
|
||||
|
||||
std::array<char, 4096> log = {};
|
||||
GLsizei length = 0;
|
||||
glGetShaderInfoLog(shader, static_cast<GLsizei>(log.size()), &length, log.data());
|
||||
error = std::string(log.data(), static_cast<std::size_t>(length));
|
||||
glDeleteShader(shader);
|
||||
shader = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
void RuntimeShaderRenderer::DestroyProgram()
|
||||
{
|
||||
if (mProgram != 0)
|
||||
glDeleteProgram(mProgram);
|
||||
if (mVertexShader != 0)
|
||||
glDeleteShader(mVertexShader);
|
||||
if (mFragmentShader != 0)
|
||||
glDeleteShader(mFragmentShader);
|
||||
mProgram = 0;
|
||||
mVertexShader = 0;
|
||||
mFragmentShader = 0;
|
||||
}
|
||||
|
||||
void RuntimeShaderRenderer::DestroyStaticGlResources()
|
||||
{
|
||||
if (mGlobalParamsBuffer != 0)
|
||||
glDeleteBuffers(1, &mGlobalParamsBuffer);
|
||||
if (mVertexArray != 0)
|
||||
glDeleteVertexArrays(1, &mVertexArray);
|
||||
if (mFallbackSourceTexture != 0)
|
||||
glDeleteTextures(1, &mFallbackSourceTexture);
|
||||
mGlobalParamsBuffer = 0;
|
||||
mGlobalParamsBufferSize = 0;
|
||||
mVertexArray = 0;
|
||||
mFallbackSourceTexture = 0;
|
||||
}
|
||||
56
src/render/runtime/RuntimeShaderRenderer.h
Normal file
56
src/render/runtime/RuntimeShaderRenderer.h
Normal file
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
#include "GLExtensions.h"
|
||||
#include "RuntimeShaderProgram.h"
|
||||
#include "../../runtime/RuntimeShaderArtifact.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class RuntimeShaderRenderer
|
||||
{
|
||||
public:
|
||||
RuntimeShaderRenderer() = default;
|
||||
RuntimeShaderRenderer(const RuntimeShaderRenderer&) = delete;
|
||||
RuntimeShaderRenderer& operator=(const RuntimeShaderRenderer&) = delete;
|
||||
~RuntimeShaderRenderer();
|
||||
|
||||
bool CommitPreparedProgram(RuntimePreparedShaderProgram& preparedProgram, std::string& error);
|
||||
bool HasProgram() const { return mProgram != 0; }
|
||||
void UpdateArtifactState(const RuntimeShaderArtifact& artifact);
|
||||
void RenderFrame(uint64_t frameIndex, unsigned width, unsigned height, GLuint sourceTexture = 0, GLuint layerInputTexture = 0);
|
||||
void ShutdownGl();
|
||||
|
||||
static bool BuildPreparedProgram(
|
||||
const std::string& layerId,
|
||||
const std::string& sourceFingerprint,
|
||||
const RuntimeShaderArtifact& artifact,
|
||||
RuntimePreparedShaderProgram& preparedProgram);
|
||||
static bool BuildPreparedPassProgram(
|
||||
const std::string& layerId,
|
||||
const std::string& sourceFingerprint,
|
||||
const RuntimeShaderArtifact& artifact,
|
||||
const RuntimeShaderPassArtifact& passArtifact,
|
||||
RuntimePreparedShaderProgram& preparedProgram);
|
||||
|
||||
private:
|
||||
bool EnsureStaticGlResources(std::string& error);
|
||||
static bool CompileShader(GLenum shaderType, const char* source, GLuint& shader, std::string& error);
|
||||
static bool BuildProgram(const std::string& fragmentShaderSource, GLuint& program, GLuint& vertexShader, GLuint& fragmentShader, std::string& error);
|
||||
static void AssignSamplerUniforms(GLuint program);
|
||||
void UpdateGlobalParams(uint64_t frameIndex, unsigned width, unsigned height);
|
||||
void BindRuntimeTextures(GLuint sourceTexture, GLuint layerInputTexture);
|
||||
void DestroyProgram();
|
||||
void DestroyStaticGlResources();
|
||||
|
||||
RuntimeShaderArtifact mArtifact;
|
||||
GLuint mProgram = 0;
|
||||
GLuint mVertexShader = 0;
|
||||
GLuint mFragmentShader = 0;
|
||||
GLuint mVertexArray = 0;
|
||||
GLuint mGlobalParamsBuffer = 0;
|
||||
GLsizeiptr mGlobalParamsBufferSize = 0;
|
||||
GLuint mFallbackSourceTexture = 0;
|
||||
std::vector<unsigned char> mGlobalParamsScratch;
|
||||
};
|
||||
Reference in New Issue
Block a user