zero_copy_video_player/glwidget.cpp

224 lines
7.6 KiB
C++

#include "glwidget.h"
#include "videodecoder.h"
#include <QOpenGLContext>
#include <QDebug>
GLWidget::GLWidget(QWidget *parent)
: QOpenGLWidget(parent)
, m_videoDecoder(nullptr)
, m_texY(0)
, m_texUV(0)
, m_vbo(0)
, m_cudaResourceY(nullptr)
, m_cudaResourceUV(nullptr)
, m_allocatedWidth(0)
, m_allocatedHeight(0)
{
m_localFrame = av_frame_alloc();
}
GLWidget::~GLWidget() {
makeCurrent();
cleanupCudaGL();
if (m_texY) {
glDeleteTextures(1, &m_texY);
}
if (m_texUV) {
glDeleteTextures(1, &m_texUV);
}
if (m_vbo) {
glDeleteBuffers(1, &m_vbo);
}
if (m_localFrame) {
av_frame_free(&m_localFrame);
m_localFrame = nullptr;
}
}
void GLWidget::setVideoDecoder(VideoDecoder *decoder) {
m_videoDecoder = decoder;
connect(m_videoDecoder, &VideoDecoder::frameReady, this, &GLWidget::onFrameReady);
}
void GLWidget::onFrameReady() {
update();
}
void GLWidget::clearVideo() {
cleanupCudaGL();
update();
}
void GLWidget::initializeGL() {
initializeOpenGLFunctions();
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
// Compilar e inicializar shaders
m_shaderProgram.addShaderFromSourceFile(QOpenGLShader::Vertex, "shader.vert");
m_shaderProgram.addShaderFromSourceFile(QOpenGLShader::Fragment, "shader.frag");
m_shaderProgram.link();
// Crear VBO para un quad de pantalla completa (espejado verticalmente para corregir la diferencia de origen OpenGL vs CUDA)
float vertices[] = {
// Posiciones // Coordenadas UV
-1.0f, -1.0f, 0.0f, 1.0f,
1.0f, -1.0f, 1.0f, 1.0f,
-1.0f, 1.0f, 0.0f, 0.0f,
-1.0f, 1.0f, 0.0f, 0.0f,
1.0f, -1.0f, 1.0f, 1.0f,
1.0f, 1.0f, 1.0f, 0.0f
};
glGenBuffers(1, &m_vbo);
glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// Generar texturas de OpenGL
glGenTextures(1, &m_texY);
glBindTexture(GL_TEXTURE_2D, m_texY);
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);
glGenTextures(1, &m_texUV);
glBindTexture(GL_TEXTURE_2D, m_texUV);
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);
}
void GLWidget::resizeGL(int w, int h) {
glViewport(0, 0, w, h);
}
void GLWidget::paintGL() {
glClear(GL_COLOR_BUFFER_BIT);
if (!m_videoDecoder) return;
// Obtener la referencia segura del fotograma CUDA decodificado en GPU
if (!m_videoDecoder->getCurrentFrameRef(m_localFrame)) {
return; // No hay frame listo todavía
}
int w = m_localFrame->width;
int h = m_localFrame->height;
// Si la resolución del video cambió o no se ha asignado aún
if (w != m_allocatedWidth || h != m_allocatedHeight) {
cleanupCudaGL();
// 1. Reservar espacio en VRAM para la textura Y (Luminancia - 1 canal de 8 bits)
glBindTexture(GL_TEXTURE_2D, m_texY);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, w, h, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr);
// 2. Reservar espacio en VRAM para la textura UV (Crominancia entrelazada - 2 canales de 8 bits)
glBindTexture(GL_TEXTURE_2D, m_texUV);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RG, w / 2, h / 2, 0, GL_RG, GL_UNSIGNED_BYTE, nullptr);
// Registrar las texturas en la API de interoperabilidad de CUDA
cudaError_t errY = cudaGraphicsGLRegisterImage(&m_cudaResourceY, m_texY, GL_TEXTURE_2D, cudaGraphicsRegisterFlagsWriteDiscard);
cudaError_t errUV = cudaGraphicsGLRegisterImage(&m_cudaResourceUV, m_texUV, GL_TEXTURE_2D, cudaGraphicsRegisterFlagsWriteDiscard);
if (errY != cudaSuccess || errUV != cudaSuccess) {
qWarning() << "Error registrando texturas en CUDA:" << cudaGetErrorString(errY) << "/" << cudaGetErrorString(errUV);
cleanupCudaGL();
av_frame_unref(m_localFrame);
return;
}
m_allocatedWidth = w;
m_allocatedHeight = h;
}
// 3. Mapear las texturas de OpenGL hacia CUDA, realizar la copia VRAM-to-VRAM, y desmapear
if (m_cudaResourceY && m_cudaResourceUV) {
cudaError_t mapY = cudaGraphicsMapResources(1, &m_cudaResourceY, 0);
cudaError_t mapUV = cudaGraphicsMapResources(1, &m_cudaResourceUV, 0);
if (mapY == cudaSuccess && mapUV == cudaSuccess) {
cudaArray_t arrayY = nullptr;
cudaArray_t arrayUV = nullptr;
cudaGraphicsSubResourceGetMappedArray(&arrayY, m_cudaResourceY, 0, 0);
cudaGraphicsSubResourceGetMappedArray(&arrayUV, m_cudaResourceUV, 0, 0);
// Copia Directa en GPU de la textura Y (Luminancia)
cudaError_t cpyY = cudaMemcpy2DToArray(
arrayY, 0, 0,
(void*)m_localFrame->data[0], m_localFrame->linesize[0],
w, h,
cudaMemcpyDeviceToDevice
);
// Copia Directa en GPU de la textura UV (Crominancia entrelazada)
cudaError_t cpyUV = cudaMemcpy2DToArray(
arrayUV, 0, 0,
(void*)m_localFrame->data[1], m_localFrame->linesize[1],
w, h / 2,
cudaMemcpyDeviceToDevice
);
if (cpyY != cudaSuccess || cpyUV != cudaSuccess) {
qWarning() << "Fallo en copias CUDA Memcpy2DToArray:" << cudaGetErrorString(cpyY) << "/" << cudaGetErrorString(cpyUV);
}
} else {
qWarning() << "Fallo al mapear texturas en CUDA:" << cudaGetErrorString(mapY) << "/" << cudaGetErrorString(mapUV);
}
// Importante: desmapear para que OpenGL recupere el control de renderizado
cudaGraphicsUnmapResources(1, &m_cudaResourceY, 0);
cudaGraphicsUnmapResources(1, &m_cudaResourceUV, 0);
}
// 4. Dibujar la escena en pantalla usando el Shader CRT
m_shaderProgram.bind();
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_texY);
m_shaderProgram.setUniformValue("u_texY", 0);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, m_texUV);
m_shaderProgram.setUniformValue("u_texUV", 1);
glBindBuffer(GL_ARRAY_BUFFER, m_vbo);
int posLocation = m_shaderProgram.attributeLocation("a_position");
m_shaderProgram.enableAttributeArray(posLocation);
glVertexAttribPointer(posLocation, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), nullptr);
int texLocation = m_shaderProgram.attributeLocation("a_texCoord");
m_shaderProgram.enableAttributeArray(texLocation);
glVertexAttribPointer(texLocation, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), reinterpret_cast<void*>(2 * sizeof(float)));
glDrawArrays(GL_TRIANGLES, 0, 6);
m_shaderProgram.disableAttributeArray(posLocation);
m_shaderProgram.disableAttributeArray(texLocation);
m_shaderProgram.release();
// Liberar la referencia local del fotograma
av_frame_unref(m_localFrame);
}
void GLWidget::cleanupCudaGL() {
makeCurrent();
if (m_cudaResourceY) {
cudaGraphicsUnregisterResource(m_cudaResourceY);
m_cudaResourceY = nullptr;
}
if (m_cudaResourceUV) {
cudaGraphicsUnregisterResource(m_cudaResourceUV);
m_cudaResourceUV = nullptr;
}
m_allocatedWidth = 0;
m_allocatedHeight = 0;
}