Merge pull request #40 from SolitudeSF/shader

Rewrite rendering and navigation pipeline to support shaders.
This commit is contained in:
Alexey Kutepov 2019-10-24 01:53:51 +07:00 committed by GitHub
commit 4131e33f37
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 221 additions and 114 deletions

8
src/boomer.fs Normal file
View File

@ -0,0 +1,8 @@
#version 130
out mediump vec4 color;
in mediump vec2 texcoord;
uniform sampler2D tex;
void main()
{
color = texture(tex, texcoord);
}

View File

@ -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
View 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;
}

View File

@ -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
View 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

View File

@ -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)

View File

@ -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)