Initial release: RadiantWave v1.0
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
package graphics
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/go-gl/gl/v3.3-core/gl"
|
||||
"github.com/veandco/go-sdl2/sdl"
|
||||
)
|
||||
|
||||
// CreateTextureFromSurface uploads an SDL surface to an OpenGL texture and returns the texture ID.
|
||||
func CreateTextureFromSurface(surface *sdl.Surface) (uint32, error) {
|
||||
if surface == nil {
|
||||
return 0, fmt.Errorf("CreateTextureFromSurface: input surface is nil")
|
||||
}
|
||||
|
||||
var textureID uint32
|
||||
var err error
|
||||
|
||||
surfaceToUpload := surface
|
||||
var convertedSurface *sdl.Surface = nil
|
||||
|
||||
sdlFormatEnum := surface.Format.Format
|
||||
var dataFormat uint32
|
||||
var internalFormat int32 = gl.RGBA8
|
||||
|
||||
if sdlFormatEnum == sdl.PIXELFORMAT_INDEX8 {
|
||||
convertedSurface, err = surface.ConvertFormat(sdl.PIXELFORMAT_ARGB8888, 0)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to convert INDEX8 surface to ARGB8888: %w", err)
|
||||
}
|
||||
defer convertedSurface.Free()
|
||||
|
||||
surfaceToUpload = convertedSurface
|
||||
sdlFormatEnum = surfaceToUpload.Format.Format
|
||||
}
|
||||
|
||||
switch sdlFormatEnum {
|
||||
case sdl.PIXELFORMAT_ARGB8888:
|
||||
dataFormat = gl.BGRA
|
||||
case sdl.PIXELFORMAT_ABGR8888:
|
||||
dataFormat = gl.RGBA
|
||||
case sdl.PIXELFORMAT_RGBA8888:
|
||||
dataFormat = gl.RGBA
|
||||
case sdl.PIXELFORMAT_BGRA8888:
|
||||
dataFormat = gl.BGRA
|
||||
default:
|
||||
return 0, fmt.Errorf("unhandled SDL pixel format for OpenGL upload: %s", sdl.GetPixelFormatName(uint(sdlFormatEnum)))
|
||||
}
|
||||
|
||||
for errCode := gl.GetError(); errCode != gl.NO_ERROR; errCode = gl.GetError() {
|
||||
log.Printf("OpenGL error pending before GenTextures: 0x%X", errCode)
|
||||
}
|
||||
|
||||
gl.GenTextures(1, &textureID)
|
||||
if errCode := gl.GetError(); errCode != gl.NO_ERROR {
|
||||
return 0, fmt.Errorf("OpenGL error 0x%X after GenTextures", errCode)
|
||||
}
|
||||
if textureID == 0 {
|
||||
return 0, fmt.Errorf("gl.GenTextures returned textureID 0")
|
||||
}
|
||||
|
||||
gl.BindTexture(gl.TEXTURE_2D, textureID)
|
||||
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
|
||||
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
|
||||
|
||||
gl.PixelStorei(gl.UNPACK_ALIGNMENT, 1)
|
||||
|
||||
expectedPitch := surfaceToUpload.W * int32(surfaceToUpload.Format.BytesPerPixel)
|
||||
if int32(surfaceToUpload.Pitch) != expectedPitch {
|
||||
gl.PixelStorei(gl.UNPACK_ROW_LENGTH, surfaceToUpload.Pitch/int32(surfaceToUpload.Format.BytesPerPixel))
|
||||
} else {
|
||||
gl.PixelStorei(gl.UNPACK_ROW_LENGTH, 0)
|
||||
}
|
||||
|
||||
gl.TexImage2D(
|
||||
gl.TEXTURE_2D,
|
||||
0,
|
||||
internalFormat,
|
||||
surfaceToUpload.W,
|
||||
surfaceToUpload.H,
|
||||
0,
|
||||
dataFormat,
|
||||
gl.UNSIGNED_BYTE,
|
||||
gl.Ptr(surfaceToUpload.Pixels()),
|
||||
)
|
||||
|
||||
gl.PixelStorei(gl.UNPACK_ALIGNMENT, 4)
|
||||
gl.PixelStorei(gl.UNPACK_ROW_LENGTH, 0)
|
||||
|
||||
if glErr := gl.GetError(); glErr != gl.NO_ERROR {
|
||||
gl.DeleteTextures(1, &textureID)
|
||||
return 0, fmt.Errorf("OpenGL error 0x%X after TexImage2D", glErr)
|
||||
}
|
||||
|
||||
gl.BindTexture(gl.TEXTURE_2D, 0)
|
||||
return textureID, nil
|
||||
}
|
||||
|
||||
// CheckGLError queries OpenGL for errors and returns the first one found.
|
||||
func CheckGLError() error {
|
||||
errCode := gl.GetError()
|
||||
if errCode == gl.NO_ERROR {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch errCode {
|
||||
case gl.INVALID_ENUM:
|
||||
return fmt.Errorf("INVALID_ENUM")
|
||||
case gl.INVALID_VALUE:
|
||||
return fmt.Errorf("INVALID_VALUE")
|
||||
case gl.INVALID_OPERATION:
|
||||
return fmt.Errorf("INVALID_OPERATION")
|
||||
case gl.STACK_OVERFLOW:
|
||||
return fmt.Errorf("STACK_OVERFLOW")
|
||||
case gl.STACK_UNDERFLOW:
|
||||
return fmt.Errorf("STACK_UNDERFLOW")
|
||||
case gl.OUT_OF_MEMORY:
|
||||
return fmt.Errorf("OUT_OF_MEMORY")
|
||||
case gl.INVALID_FRAMEBUFFER_OPERATION:
|
||||
return fmt.Errorf("INVALID_FRAMEBUFFER_OPERATION")
|
||||
default:
|
||||
return fmt.Errorf("Unknown GL error: 0x%X", errCode)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user