Files
radiantwave-legacy/internal/graphics/virtual_framebuffer.go
T

194 lines
5.1 KiB
Go

package graphics
import (
"fmt"
"github.com/go-gl/gl/v3.3-core/gl"
)
const (
VirtualWidth int32 = 1920
VirtualHeight int32 = 1080
)
var (
vfbo uint32
vfboTexture uint32
vfboDepthStencil uint32
blitVAO uint32
blitVBO uint32
blitShaderProg uint32
nativeW, nativeH int32
)
// InitVirtualFramebuffer creates the FBO at virtual resolution and the blit shader.
// Call after OpenGL context is created.
func InitVirtualFramebuffer(displayW, displayH int32) error {
nativeW = displayW
nativeH = displayH
// --- FBO setup ---
gl.GenFramebuffers(1, &vfbo)
gl.BindFramebuffer(gl.FRAMEBUFFER, vfbo)
gl.GenTextures(1, &vfboTexture)
gl.BindTexture(gl.TEXTURE_2D, vfboTexture)
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, VirtualWidth, VirtualHeight, 0, gl.RGBA, gl.UNSIGNED_BYTE, nil)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, vfboTexture, 0)
// Depth/stencil renderbuffer
gl.GenRenderbuffers(1, &vfboDepthStencil)
gl.BindRenderbuffer(gl.RENDERBUFFER, vfboDepthStencil)
gl.RenderbufferStorage(gl.RENDERBUFFER, gl.DEPTH24_STENCIL8, VirtualWidth, VirtualHeight)
gl.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, vfboDepthStencil)
if status := gl.CheckFramebufferStatus(gl.FRAMEBUFFER); status != gl.FRAMEBUFFER_COMPLETE {
return fmt.Errorf("virtual framebuffer incomplete: 0x%X", status)
}
gl.BindFramebuffer(gl.FRAMEBUFFER, 0)
// --- Fullscreen quad ---
vertices := []float32{
// pos (x,y) texcoord (u,v)
-1, -1, 0, 0,
1, -1, 1, 0,
-1, 1, 0, 1,
1, 1, 1, 1,
}
gl.GenVertexArrays(1, &blitVAO)
gl.GenBuffers(1, &blitVBO)
gl.BindVertexArray(blitVAO)
gl.BindBuffer(gl.ARRAY_BUFFER, blitVBO)
gl.BufferData(gl.ARRAY_BUFFER, len(vertices)*4, gl.Ptr(vertices), gl.STATIC_DRAW)
gl.EnableVertexAttribArray(0)
gl.VertexAttribPointerWithOffset(0, 2, gl.FLOAT, false, 4*4, 0)
gl.EnableVertexAttribArray(1)
gl.VertexAttribPointerWithOffset(1, 2, gl.FLOAT, false, 4*4, 2*4)
gl.BindVertexArray(0)
// --- Blit shader ---
var err error
blitShaderProg, err = compileBlitShader()
if err != nil {
return fmt.Errorf("blit shader compilation failed: %w", err)
}
return nil
}
// BeginVirtualRender binds the FBO so all subsequent draws go to the virtual 1080p buffer.
func BeginVirtualRender() {
gl.BindFramebuffer(gl.FRAMEBUFFER, vfbo)
gl.Viewport(0, 0, VirtualWidth, VirtualHeight)
}
// BlitToScreen draws the virtual buffer to the actual screen at native resolution.
func BlitToScreen() {
gl.BindFramebuffer(gl.FRAMEBUFFER, 0)
gl.Viewport(0, 0, nativeW, nativeH)
gl.ClearColor(0, 0, 0, 1)
gl.Clear(gl.COLOR_BUFFER_BIT)
gl.UseProgram(blitShaderProg)
gl.ActiveTexture(gl.TEXTURE0)
gl.BindTexture(gl.TEXTURE_2D, vfboTexture)
gl.BindVertexArray(blitVAO)
gl.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)
gl.BindVertexArray(0)
}
// DestroyVirtualFramebuffer cleans up all FBO resources.
func DestroyVirtualFramebuffer() {
if blitShaderProg != 0 {
gl.DeleteProgram(blitShaderProg)
}
if blitVBO != 0 {
gl.DeleteBuffers(1, &blitVBO)
}
if blitVAO != 0 {
gl.DeleteVertexArrays(1, &blitVAO)
}
if vfboDepthStencil != 0 {
gl.DeleteRenderbuffers(1, &vfboDepthStencil)
}
if vfboTexture != 0 {
gl.DeleteTextures(1, &vfboTexture)
}
if vfbo != 0 {
gl.DeleteFramebuffers(1, &vfbo)
}
}
func compileBlitShader() (uint32, error) {
vertSrc := `#version 330 core
layout(location = 0) in vec2 aPos;
layout(location = 1) in vec2 aTexCoord;
out vec2 vTexCoord;
void main() {
vTexCoord = aTexCoord;
gl_Position = vec4(aPos, 0.0, 1.0);
}` + "\x00"
fragSrc := `#version 330 core
in vec2 vTexCoord;
out vec4 FragColor;
uniform sampler2D screenTexture;
void main() {
FragColor = texture(screenTexture, vTexCoord);
}` + "\x00"
vertShader, err := compileShader(vertSrc, gl.VERTEX_SHADER)
if err != nil {
return 0, err
}
fragShader, err := compileShader(fragSrc, gl.FRAGMENT_SHADER)
if err != nil {
return 0, err
}
prog := gl.CreateProgram()
gl.AttachShader(prog, vertShader)
gl.AttachShader(prog, fragShader)
gl.LinkProgram(prog)
var status int32
gl.GetProgramiv(prog, gl.LINK_STATUS, &status)
if status == gl.FALSE {
var logLen int32
gl.GetProgramiv(prog, gl.INFO_LOG_LENGTH, &logLen)
logStr := make([]byte, logLen)
gl.GetProgramInfoLog(prog, logLen, nil, &logStr[0])
return 0, fmt.Errorf("shader link error: %s", string(logStr))
}
gl.DeleteShader(vertShader)
gl.DeleteShader(fragShader)
return prog, nil
}
func compileShader(src string, shaderType uint32) (uint32, error) {
shader := gl.CreateShader(shaderType)
csrc, free := gl.Strs(src)
gl.ShaderSource(shader, 1, csrc, nil)
free()
gl.CompileShader(shader)
var status int32
gl.GetShaderiv(shader, gl.COMPILE_STATUS, &status)
if status == gl.FALSE {
var logLen int32
gl.GetShaderiv(shader, gl.INFO_LOG_LENGTH, &logLen)
logStr := make([]byte, logLen)
gl.GetShaderInfoLog(shader, logLen, nil, &logStr[0])
return 0, fmt.Errorf("shader compile error: %s", string(logStr))
}
return shader, nil
}