mirror of
https://github.com/tsoding/boomer
synced 2024-11-16 12:12:47 +00:00
Merge pull request #40 from SolitudeSF/shader
Rewrite rendering and navigation pipeline to support shaders.
This commit is contained in:
commit
4131e33f37
8
src/boomer.fs
Normal file
8
src/boomer.fs
Normal file
@ -0,0 +1,8 @@
|
||||
#version 130
|
||||
out mediump vec4 color;
|
||||
in mediump vec2 texcoord;
|
||||
uniform sampler2D tex;
|
||||
void main()
|
||||
{
|
||||
color = texture(tex, texcoord);
|
||||
}
|
195
src/boomer.nim
195
src/boomer.nim
@ -1,46 +1,71 @@
|
||||
import os
|
||||
|
||||
import x11/xlib, x11/x, x11/xutil
|
||||
import opengl, opengl/glx
|
||||
|
||||
import vec2
|
||||
import navigation
|
||||
import image
|
||||
import config
|
||||
|
||||
template checkError(context: string) =
|
||||
let error = glGetError()
|
||||
if error != 0.GLenum:
|
||||
echo "GL error ", error.GLint, " ", context
|
||||
import x11/xlib, x11/x, x11/xutil
|
||||
import opengl, opengl/glx
|
||||
import la
|
||||
|
||||
proc display(screenshot: Image, camera: Camera) =
|
||||
glClearColor(0.0, 0.0, 0.0, 1.0)
|
||||
const
|
||||
isDebug = not (defined(danger) or defined(release))
|
||||
vertexShader = slurp "boomer.vs"
|
||||
fragmentShader = slurp "boomer.fs"
|
||||
|
||||
proc newShader(shader: string, kind: GLenum, filePath: string): GLuint =
|
||||
result = glCreateShader(kind)
|
||||
var shaderArray = allocCStringArray([shader])
|
||||
glShaderSource(result, 1, shaderArray, nil)
|
||||
glCompileShader(result)
|
||||
deallocCStringArray(shaderArray)
|
||||
when isDebug:
|
||||
var success: GLint
|
||||
var infoLog = newString(512).cstring
|
||||
glGetShaderiv(result, GL_COMPILE_STATUS, addr success)
|
||||
if not success.bool:
|
||||
glGetShaderInfoLog(result, 512, nil, infoLog)
|
||||
echo "------------------------------"
|
||||
echo "Error during compiling shader: ", filePath, ". Log:"
|
||||
echo infoLog
|
||||
echo "------------------------------"
|
||||
|
||||
proc newShaderProgram(vertex, fragment: string): GLuint =
|
||||
result = glCreateProgram()
|
||||
|
||||
# TODO: filename for shader compilation error reporting are hardcoded
|
||||
var
|
||||
vertexShader = newShader(vertex, GL_VERTEX_SHADER, "boomer.vs")
|
||||
fragmentShader = newShader(fragment, GL_FRAGMENT_SHADER, "boomer.fs")
|
||||
|
||||
glAttachShader(result, vertexShader)
|
||||
glAttachShader(result, fragmentShader)
|
||||
|
||||
glLinkProgram(result)
|
||||
|
||||
glDeleteShader(vertexShader)
|
||||
glDeleteShader(fragmentShader)
|
||||
|
||||
when isDebug:
|
||||
var success: GLint
|
||||
var infoLog = newString(512).cstring
|
||||
glGetProgramiv(result, GL_LINK_STATUS, addr success)
|
||||
if not success.bool:
|
||||
glGetProgramInfoLog(result, 512, nil, infoLog)
|
||||
echo infoLog
|
||||
|
||||
glUseProgram(result)
|
||||
|
||||
proc draw(screenshot: Image, camera: var Camera, shader, vao, texture: GLuint) =
|
||||
glClearColor(0.1, 0.1, 0.1, 1.0)
|
||||
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT)
|
||||
|
||||
glPushMatrix()
|
||||
glUseProgram(shader)
|
||||
|
||||
glScalef(camera.scale, camera.scale, 1.0)
|
||||
glTranslatef(-camera.position.x, -camera.position.y, 0.0)
|
||||
glUniformMatrix4fv(glGetUniformLocation(shader, "transform".cstring), 1, false, camera.matrix.caddr)
|
||||
|
||||
glBegin(GL_QUADS)
|
||||
glTexCoord2i(0, 0)
|
||||
glVertex2f(0.0, 0.0)
|
||||
|
||||
glTexCoord2i(1, 0)
|
||||
glVertex2f(screenshot.width.float, 0.0)
|
||||
|
||||
glTexCoord2i(1, 1)
|
||||
glVertex2f(screenshot.width.float, screenshot.height.float)
|
||||
|
||||
glTexCoord2i(0, 1)
|
||||
glVertex2f(0.0, screenshot.height.float)
|
||||
glEnd()
|
||||
checkError("rasterizing the quadrangle")
|
||||
|
||||
glFlush()
|
||||
checkError("flush")
|
||||
|
||||
glPopMatrix()
|
||||
glBindVertexArray(vao)
|
||||
glDrawElements(GL_TRIANGLES, count = 6, GL_UNSIGNED_INT, indices = nil)
|
||||
|
||||
# TODO(#29): get rid of custom X11 button constants
|
||||
const
|
||||
@ -63,6 +88,8 @@ proc main() =
|
||||
|
||||
echo "Using config: ", config
|
||||
|
||||
# Fetching pixel data from X
|
||||
|
||||
var display = XOpenDisplay(nil)
|
||||
if display == nil:
|
||||
quit "Failed to open display"
|
||||
@ -75,21 +102,21 @@ proc main() =
|
||||
assert screenshot.bpp == 32
|
||||
|
||||
let screen = XDefaultScreen(display)
|
||||
var glxMajor : int
|
||||
var glxMinor : int
|
||||
var glxMajor, glxMinor: int
|
||||
|
||||
if (not glXQueryVersion(display, glxMajor, glxMinor) or
|
||||
(glxMajor == 1 and glxMinor < 3) or
|
||||
(glxMajor < 1)):
|
||||
quit "Invalid GLX version. Expected >=1.3"
|
||||
echo("GLX version ", glxMajor, ".", glxMinor)
|
||||
echo("GLX extension: ", $glXQueryExtensionsString(display, screen))
|
||||
echo("GLX extension: ", glXQueryExtensionsString(display, screen))
|
||||
|
||||
var attrs = [
|
||||
GLX_RGBA,
|
||||
GLX_DEPTH_SIZE, 24,
|
||||
GLX_DOUBLEBUFFER,
|
||||
None]
|
||||
None
|
||||
]
|
||||
|
||||
var vi = glXChooseVisual(display, 0, addr attrs[0])
|
||||
if vi == nil:
|
||||
@ -130,12 +157,50 @@ proc main() =
|
||||
|
||||
loadExtensions()
|
||||
|
||||
var textures: GLuint = 0
|
||||
glGenTextures(1, addr textures)
|
||||
checkError("making texture")
|
||||
var shaderProgram = newShaderProgram(vertexShader, fragmentShader)
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, textures)
|
||||
checkError("binding texture")
|
||||
var
|
||||
vao, vbo, ebo: GLuint
|
||||
vertices = [
|
||||
# Position Texture coords
|
||||
[GLfloat 1.0, -1.0, 0.0, 1.0, 1.0], # Top right
|
||||
[GLfloat 1.0, 1.0, 0.0, 1.0, 0.0], # Bottom right
|
||||
[GLfloat -1.0, 1.0, 0.0, 0.0, 0.0], # Bottom left
|
||||
[GLfloat -1.0, -1.0, 0.0, 0.0, 1.0] # Top left
|
||||
]
|
||||
indices = [GLuint(0), 1, 3,
|
||||
1, 2, 3]
|
||||
|
||||
glGenVertexArrays(1, addr vao)
|
||||
glGenBuffers(1, addr vbo)
|
||||
glGenBuffers(1, addr ebo)
|
||||
defer:
|
||||
glDeleteVertexArrays(1, addr vao)
|
||||
glDeleteBuffers(1, addr vbo)
|
||||
glDeleteBuffers(1, addr ebo)
|
||||
|
||||
glBindVertexArray(vao)
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo)
|
||||
glBufferData(GL_ARRAY_BUFFER, size = GLsizeiptr(sizeof(vertices)),
|
||||
addr vertices, GL_STATIC_DRAW)
|
||||
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, size = GLsizeiptr(sizeof(indices)),
|
||||
addr indices, GL_STATIC_DRAW);
|
||||
|
||||
var stride = GLsizei(vertices[0].len * sizeof(GLfloat))
|
||||
|
||||
glVertexAttribPointer(0, 3, cGL_FLOAT, false, stride, cast[pointer](0))
|
||||
glEnableVertexAttribArray(0)
|
||||
|
||||
glVertexAttribPointer(1, 2, cGL_FLOAT, false, stride, cast[pointer](3 * sizeof(GLfloat)))
|
||||
glEnableVertexAttribArray(1)
|
||||
|
||||
var texture = 0.GLuint
|
||||
glGenTextures(1, addr texture)
|
||||
glActiveTexture(GL_TEXTURE0)
|
||||
glBindTexture(GL_TEXTURE_2D, texture)
|
||||
|
||||
glTexImage2D(GL_TEXTURE_2D,
|
||||
0,
|
||||
@ -147,27 +212,28 @@ proc main() =
|
||||
GL_BGRA,
|
||||
GL_UNSIGNED_BYTE,
|
||||
screenshot.pixels)
|
||||
checkError("loading texture")
|
||||
glGenerateMipmap(GL_TEXTURE_2D)
|
||||
|
||||
glUniform1i(glGetUniformLocation(shaderProgram, "tex".cstring), 0)
|
||||
|
||||
|
||||
glEnable(GL_TEXTURE_2D)
|
||||
|
||||
glOrtho(0.0, screenshot.width.float,
|
||||
screenshot.height.float, 0.0,
|
||||
-1.0, 1.0)
|
||||
checkError("setting transforms")
|
||||
|
||||
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_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_BORDER)
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER)
|
||||
|
||||
glViewport(0, 0, screenshot.width, screenshot.height)
|
||||
|
||||
var quitting = false
|
||||
var camera = Camera(scale: 1.0)
|
||||
var mouse: Mouse
|
||||
var
|
||||
quitting = false
|
||||
camera = Camera(scale: 1.0, matrix: mat4f(1))
|
||||
mouse: Mouse
|
||||
|
||||
while not quitting:
|
||||
var wa: TXWindowAttributes
|
||||
@ -181,12 +247,13 @@ proc main() =
|
||||
discard
|
||||
|
||||
of MotionNotify:
|
||||
mouse.curr = (xev.xmotion.x.float,
|
||||
xev.xmotion.y.float)
|
||||
mouse.curr = vec2(xev.xmotion.x.float32 / screenshot.width.float32 * 2.0'f32 - 1.0'f32,
|
||||
xev.xmotion.y.float32 / screenshot.height.float32 * 2.0'f32 - 1.0'f32)
|
||||
|
||||
if mouse.drag:
|
||||
camera.position += camera.world(mouse.prev) - camera.world(mouse.curr)
|
||||
camera.velocity = (mouse.prev - mouse.curr) * config.drag_velocity_factor
|
||||
let mouseDelta = mouse.prev - mouse.curr
|
||||
camera.position += mouseDelta
|
||||
camera.velocity = mouseDelta * config.dragVelocityFactor
|
||||
mouse.prev = mouse.curr
|
||||
|
||||
of ClientMessage:
|
||||
@ -197,13 +264,13 @@ proc main() =
|
||||
case xev.xkey.keycode
|
||||
of 19:
|
||||
camera.scale = 1.0
|
||||
camera.delta_scale = 0.0
|
||||
camera.position = (0.0, 0.0)
|
||||
camera.velocity = (0.0, 0.0)
|
||||
camera.deltaScale = 0.0
|
||||
camera.position = vec2(0.0'f32, 0.0)
|
||||
camera.velocity = vec2(0.0'f32, 0.0)
|
||||
of 24:
|
||||
quitting = true
|
||||
of 27:
|
||||
if configFile.len > 0:
|
||||
if configFile.len > 0 and existsFile(configFile):
|
||||
config = loadConfig(configFile)
|
||||
else:
|
||||
discard
|
||||
@ -215,10 +282,10 @@ proc main() =
|
||||
mouse.drag = true
|
||||
|
||||
of WHEEL_UP:
|
||||
camera.delta_scale += config.scroll_speed
|
||||
camera.deltaScale += config.scrollSpeed
|
||||
|
||||
of WHEEL_DOWN:
|
||||
camera.delta_scale -= config.scroll_speed
|
||||
camera.deltaScale -= config.scrollSpeed
|
||||
|
||||
else:
|
||||
discard
|
||||
@ -234,10 +301,8 @@ proc main() =
|
||||
|
||||
camera.update(config, 1.0 / config.fps.float, mouse)
|
||||
|
||||
screenshot.display(camera)
|
||||
screenshot.draw(camera, shaderProgram, vao, texture)
|
||||
|
||||
glXSwapBuffers(display, win)
|
||||
|
||||
# saveToPPM("screenshot.ppm", image)
|
||||
|
||||
main()
|
||||
|
10
src/boomer.vs
Normal file
10
src/boomer.vs
Normal file
@ -0,0 +1,10 @@
|
||||
#version 130
|
||||
in vec3 aPos;
|
||||
in vec2 aTexCoord;
|
||||
out vec2 texcoord;
|
||||
uniform mat4 transform;
|
||||
void main()
|
||||
{
|
||||
gl_Position = transform * vec4(aPos, 1.0f);
|
||||
texcoord = aTexCoord;
|
||||
}
|
@ -5,13 +5,15 @@ type Config* = object
|
||||
dragVelocityFactor*: float
|
||||
dragFriction*: float
|
||||
scaleFriction*: float
|
||||
scalePanning*: float
|
||||
fps*: int
|
||||
|
||||
const defaultConfig* = Config(
|
||||
scroll_speed: 1.0,
|
||||
drag_velocity_factor: 20.0,
|
||||
drag_friction: 2000.0,
|
||||
scale_friction: 5.0,
|
||||
scrollSpeed: 1.0,
|
||||
dragVelocityFactor: 10.0,
|
||||
dragFriction: 1.0,
|
||||
scaleFriction: 10.0,
|
||||
scalePanning: 0.05,
|
||||
fps: 60
|
||||
)
|
||||
|
||||
|
51
src/la.nim
Normal file
51
src/la.nim
Normal file
@ -0,0 +1,51 @@
|
||||
import math
|
||||
|
||||
type Vec2f* = tuple[x: float32, y: float32]
|
||||
type Mat4f* = array[0 .. 15, float32]
|
||||
|
||||
proc vec2*(x: float32, y: float32): Vec2f = (x, y)
|
||||
|
||||
proc caddr*(a: var Mat4f): ptr float32 =
|
||||
addr a[0]
|
||||
|
||||
proc `*`*(a: Vec2f, s: float32): Vec2f =
|
||||
(a.x * s, a.y * s)
|
||||
|
||||
proc `-`*(a: Vec2f, b: Vec2f): Vec2f =
|
||||
(a.x - b.x, a.y - b.y)
|
||||
|
||||
proc `+=`*(a: var Vec2f, b: Vec2f) =
|
||||
a.x += b.x
|
||||
a.y += b.y
|
||||
|
||||
proc `-=`*(a: var Vec2f, b: Vec2f) =
|
||||
a.x -= b.x
|
||||
a.y -= b.y
|
||||
|
||||
proc length*(a: Vec2f): float32 =
|
||||
sqrt(a.x * a.x + a.y * a.y)
|
||||
|
||||
proc normalize*(a: Vec2f): Vec2f =
|
||||
let b = a.length
|
||||
if b == 0.0'f32:
|
||||
return (0.0'f32, 0.0'f32)
|
||||
else:
|
||||
return (a.x / b, a.y / b)
|
||||
|
||||
proc mat4f*(x: float32): Mat4f =
|
||||
[x, 0.0'f32, 0.0'f32, 0.0'f32,
|
||||
0.0'f32, x, 0.0'f32, 0.0'f32,
|
||||
0.0'f32, 0.0'f32, x, 0.0'f32,
|
||||
0.0'f32, 0.0'f32, 0.0'f32, x]
|
||||
|
||||
proc translate*(mat: Mat4f, x: float32, y: float32, z: float32): Mat4f =
|
||||
result = mat
|
||||
result[12] += x
|
||||
result[13] += y
|
||||
result[14] += z
|
||||
|
||||
proc scale*(mat: Mat4f, s: float32): Mat4f =
|
||||
result = mat
|
||||
result[0 * 4 + 0] *= s
|
||||
result[1 * 4 + 1] *= s
|
||||
result[2 * 4 + 2] *= s
|
@ -1,33 +1,28 @@
|
||||
import vec2
|
||||
import math
|
||||
import config
|
||||
import la
|
||||
|
||||
type Mouse* = object
|
||||
curr*: Vec2
|
||||
prev*: Vec2
|
||||
curr*: Vec2f
|
||||
prev*: Vec2f
|
||||
drag*: bool
|
||||
|
||||
type Camera* = object
|
||||
position*: Vec2
|
||||
velocity*: Vec2
|
||||
position*: Vec2f
|
||||
velocity*: Vec2f
|
||||
scale*: float
|
||||
delta_scale*: float
|
||||
|
||||
proc world*(camera: Camera, v: Vec2): Vec2 =
|
||||
(v - camera.position) / camera.scale
|
||||
|
||||
proc screen*(camera: Camera, v: Vec2): Vec2 =
|
||||
v * camera.scale + camera.position
|
||||
deltaScale*: float
|
||||
matrix*: Mat4f
|
||||
|
||||
proc update*(camera: var Camera, config: Config, dt: float, mouse: Mouse) =
|
||||
if abs(camera.delta_scale) > 0.5:
|
||||
let wp0 = camera.world(mouse.curr)
|
||||
camera.scale = max(camera.scale + camera.delta_scale * dt, 1.0)
|
||||
let wp1 = camera.world(mouse.curr)
|
||||
let dwp = wp0 - wp1
|
||||
camera.position += dwp
|
||||
camera.delta_scale -= sgn(camera.delta_scale).float * config.scale_friction * dt
|
||||
if abs(camera.deltaScale) > 0.5:
|
||||
camera.scale = max(camera.scale + camera.deltaScale * dt, 1.0)
|
||||
if camera.scale > 1.0:
|
||||
camera.position += mouse.curr * (camera.deltaScale / camera.scale * config.scalePanning)
|
||||
camera.deltaScale -= sgn(camera.deltaScale).float * config.scaleFriction * dt
|
||||
|
||||
if not mouse.drag and (camera.velocity.len > 20.0):
|
||||
if not mouse.drag and (camera.velocity.length > 0.01):
|
||||
camera.position += camera.velocity * dt
|
||||
camera.velocity = camera.velocity - camera.velocity.norm * config.drag_friction * dt
|
||||
camera.velocity -= camera.velocity.normalize * (config.dragFriction * dt)
|
||||
|
||||
camera.matrix = mat4f(1).translate(-camera.position.x, camera.position.y, 0).scale(camera.scale)
|
||||
|
24
src/vec2.nim
24
src/vec2.nim
@ -1,24 +0,0 @@
|
||||
import math
|
||||
|
||||
type Vec2* = tuple[x: float, y: float]
|
||||
|
||||
proc `-`*(v1: Vec2, v2: Vec2): Vec2 {.inline.} = (v1.x - v2.x, v1.y - v2.y)
|
||||
proc `+`*(v1: Vec2, v2: Vec2): Vec2 {.inline.} = (v1.x + v2.x, v1.y + v2.y)
|
||||
proc `*`*(v: Vec2, s: float): Vec2 {.inline.} = (v.x * s, v.y * s)
|
||||
proc `/`*(v: Vec2, s: float): Vec2 {.inline.} = (v.x / s, v.y / s)
|
||||
proc `+=`*(v1: var Vec2, v2: Vec2) {.inline.} =
|
||||
v1.x += v2.x
|
||||
v1.y += v2.y
|
||||
proc `*=`*(v: var Vec2, s: float) {.inline.} =
|
||||
v.x *= s
|
||||
v.y *= s
|
||||
|
||||
proc len*(v: Vec2): float {.inline.} =
|
||||
sqrt(v.x * v.x + v.y * v.y)
|
||||
|
||||
proc norm*(v: Vec2): Vec2 {.inline.} =
|
||||
let l = v.len
|
||||
if abs(l) < 1e-9:
|
||||
result = (0.0, 0.0)
|
||||
else:
|
||||
result = (v.x / l, v.y / l)
|
Loading…
Reference in New Issue
Block a user