342 lines
9.9 KiB
C++
342 lines
9.9 KiB
C++
#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;
|
|
}
|