From ba46a329845df51d9d874dc8e6a9e9b7ecbe7005 Mon Sep 17 00:00:00 2001 From: sebastian Date: Wed, 5 Aug 2026 10:50:36 -0300 Subject: [PATCH] Se crea .md --- Guia_Arquitectura_Video_CUDA_GL.docx | Bin 10090 -> 0 bytes Guia_Arquitectura_Video_CUDA_GL.md | 111 +++++++++++++++++++++++++++ Interoperabilidad_CUDA_GL.docx | Bin 10000 -> 0 bytes 3 files changed, 111 insertions(+) delete mode 100644 Guia_Arquitectura_Video_CUDA_GL.docx create mode 100644 Guia_Arquitectura_Video_CUDA_GL.md delete mode 100644 Interoperabilidad_CUDA_GL.docx diff --git a/Guia_Arquitectura_Video_CUDA_GL.docx b/Guia_Arquitectura_Video_CUDA_GL.docx deleted file mode 100644 index e0c8c4673a9d3f4861e6ea8e9472ab5963ea838f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10090 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 fpN8Bg{$FEdata[0], frame->linesize[0], w, h, cudaMemcpyDeviceToDevice); +cudaMemcpy2DToArray(arrayUV, 0, 0, (void*)frame->data[1], frame->linesize[1], w, h / 2, cudaMemcpyDeviceToDevice); + +// Devolución del control de las texturas a OpenGL +cudaGraphicsUnmapResources(1, &m_cudaResourceY, 0); +``` + +### Punto 4: Conversión Cromática y Filtros en el Fragment Shader + +Para corregir el origen invertido entre OpenGL y CUDA, las coordenadas del Quad están configuradas con el eje vertical invertido ($V = 1.0 - V$). En el fragment shader [shader.frag](file:///home/sebastian/repos/zero_copy_video_player/shader.frag), leemos ambas texturas en paralelo y realizamos la conversión matemática de NV12 a RGB: + +```glsl +float y = texture(u_texY, uv).r; +vec2 uvVal = texture(u_texUV, uv).rg; +float u = uvVal.r - 0.5; +float v = uvVal.g - 0.5; + +float r = y + 1.402 * v; +float g = y - 0.344136 * u - 0.714136 * v; +float b = y + 1.772 * u; +vec3 color = vec3(r, g, b); +``` + +Por último, sobre esta señal de color RGB, se aplican los filtros de curvatura 3D de la lente, líneas de barrido (scanlines), aberración cromática y viñeteado. + +--- + +## 4. Resolución de Bugs Críticos en el Proyecto + +### Efecto Espejo (Inversión de Coordenadas Y - OpenGL vs CUDA) +OpenGL lee las texturas desde la esquina inferior izquierda, mientras que CUDA genera los frames con su origen en la esquina superior izquierda. La imagen aparecía invertida verticalmente. Esto se corrigió invirtiendo directamente la coordenada vertical de textura en el Quad de OpenGL en C++, evitando añadir costo aritmético al Fragment Shader. + +### Tono Verde (Copia de Crominancia UV en bytes) +El formato de video NV12 almacena el color agrupando U y V de forma entrelazada (2 bytes por píxel). Aunque el número de píxeles horizontales es de $W/2$, el ancho en bytes es de **$2 \times (W / 2) = W$ bytes**. La API de CUDA `cudaMemcpy2DToArray` exige el ancho en bytes. + +Al indicarle previamente `w / 2`, solo se copiaba el lado izquierdo de los datos de color de la imagen. La mitad derecha del buffer se mantía en `0.0`. Al procesar la conversión matemática YUV-a-RGB en el fragment shader, una crominancia nula se desvía fuertemente hacia una tonalidad verde. Se corrigió modificando el ancho de transferencia de la copia a `w` bytes en `glwidget.cpp`. + +--- + +*Guía de Integración Técnica • Debian 13 • GPU NVIDIA* diff --git a/Interoperabilidad_CUDA_GL.docx b/Interoperabilidad_CUDA_GL.docx deleted file mode 100644 index 1305f1b132b07ec881f5a93e1cf057d7d634ab32..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10000 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 AR{#J2