|
# File: nim_opengl_shader_example.nim |
|
# Author: Benjamin N. Summerton <define-private-public> |
|
# Description: An example of how to use OpenGL shaders in Nim. Draws a simple |
|
# rotating hexagon with 6 colors. Also uses indexed drawing. |
|
# |
|
# Uses this GLFW binding: https://github.com/rafaelvasco/nimrod-glfw, though if |
|
# want to use SDL2, changing out the windowing should be simple. |
|
# |
|
# This was built on Linux using OpenGL ES 3. If you want to use a different |
|
# OpenGL version then you will have to alter the shader source. |
|
# |
|
# If you want to turn on Anti-Alising, add "aa" in the command line. If you |
|
# want to change the rotation speed, input a non-negative floating point number. |
|
# Enter "0" if you don't want the hexagon to rotate. |
|
# |
|
# At the time of writing. This file used Nim 0.15.x |
|
|
|
include system/timers |
|
import math |
|
import parseopt2 |
|
import strutils |
|
import opengl |
|
import glfw3 as glfw |
|
|
|
|
|
# Runtime command line arguments |
|
var |
|
useAA = false # Anti-Aliasing |
|
rotationSpeed:float = 5 # sec/rotation |
|
|
|
# Get CLI options |
|
for kind, key, value in getopt(): |
|
if kind == cmdArgument: |
|
if key == "aa": |
|
# enable Anti-Aliasing |
|
useAA = true |
|
else: |
|
# Assume it's the rotation speed |
|
rotationSpeed = key.parseFloat |
|
if rotationSpeed < 0: |
|
rotationSpeed = 0 |
|
|
|
|
|
# init libraries |
|
if glfw.Init() == 0: |
|
raise newException(Exception, "Failed to intialize GLFW") |
|
|
|
# Turn on anti-aliasing? |
|
if useAA: |
|
glfw.WindowHint(glfw.SAMPLES, 16) |
|
|
|
# Open a window |
|
var window = glfw.CreateWindow(800, 800, "OpenGL/GLFW Shader Example (in Nim)", nil, nil) |
|
glfw.MakeContextCurrent(window) |
|
|
|
# Load opengl |
|
loadExtensions() |
|
|
|
# Print some info |
|
echo(glfw.GetVersionString()) |
|
|
|
# The data for (and about) OpenGL |
|
var |
|
# Vertex data |
|
vertices: array[21, GLfloat] = [ |
|
# Center point |
|
0'f32, 0'f32, 0'f32, |
|
|
|
# Outer points |
|
1 * cos(0 * (PI / 3)).GLfloat, 1 * sin(0 * (PI / 3)).GLfloat, 0'f32, |
|
1 * cos(1 * (PI / 3)).GLfloat, 1 * sin(1 * (PI / 3)).GLfloat, 0'f32, |
|
1 * cos(2 * (PI / 3)).GLfloat, 1 * sin(2 * (PI / 3)).GLfloat, 0'f32, |
|
1 * cos(3 * (PI / 3)).GLfloat, 1 * sin(3 * (PI / 3)).GLfloat, 0'f32, |
|
1 * cos(4 * (PI / 3)).GLfloat, 1 * sin(4 * (PI / 3)).GLfloat, 0'f32, |
|
1 * cos(5 * (PI / 3)).GLfloat, 1 * sin(5 * (PI / 3)).GLfloat, 0'f32, |
|
] |
|
|
|
# Color data |
|
colors: array[21, GLfloat] = [ |
|
# Center point |
|
1'f32, 1'f32, 1'f32, # White |
|
|
|
# Outer points |
|
1'f32, 0'f32, 0'f32, # Red |
|
1'f32, 1'f32, 0'f32, # Yellow |
|
0'f32, 1'f32, 0'f32, # Green |
|
0'f32, 1'f32, 1'f32, # Cyan |
|
0'f32, 0'f32, 1'f32, # Blue |
|
1'f32, 0'f32, 1'f32, # Magenta |
|
] |
|
|
|
# Index data |
|
indices: array[8, GLushort] = [ |
|
0'u16, |
|
1'u16, |
|
2'u16, |
|
3'u16, |
|
4'u16, |
|
5'u16, |
|
6'u16, |
|
1'u16 |
|
] |
|
|
|
# OpenGL data |
|
vertexVBO: GLuint = 0 |
|
colorVBO: GLuint = 0 |
|
vao: GLuint = 0 |
|
vertShader: GLuint |
|
fragShader: GLuint |
|
shaderProgram: GLuint |
|
|
|
# For rotating the model |
|
rotation: GLfloat = 0.0 |
|
rotationLoc: GLint |
|
|
|
# Shader source |
|
vertShaderSrc = readFile("shader.vert") |
|
fragShaderSrc = readFile("shader.frag") |
|
vertShaderArray = allocCStringArray([vertShaderSrc]) # dealloc'd at the end |
|
fragShaderArray = allocCStringArray([fragShaderSrc]) # dealloc'd at the end |
|
|
|
# Status variables |
|
isCompiled: GLint |
|
isLinked: GLint |
|
|
|
# Bind the vertices |
|
glGenBuffers(1, vertexVBO.addr) |
|
glBindBuffer(GL_ARRAY_BUFFER, vertexVBO) |
|
glBufferData(GL_ARRAY_BUFFER, vertices.sizeof, vertices.addr, GL_STATIC_DRAW) |
|
|
|
# Bind the colors |
|
glGenBuffers(1, colorVBO.addr) |
|
glBindBuffer(GL_ARRAY_BUFFER, colorVBO) |
|
glBufferData(GL_ARRAY_BUFFER, colors.sizeof, colors.addr, GL_STATIC_DRAW) |
|
|
|
# The array object |
|
glGenVertexArrays(1, vao.addr) |
|
glBindVertexArray(vao) |
|
glBindBuffer(GL_ARRAY_BUFFER, vertexVBO); |
|
glVertexAttribPointer(0, 3, cGL_FLOAT, GL_FALSE, 0, nil) |
|
glBindBuffer(GL_ARRAY_BUFFER, colorVBO); |
|
glVertexAttribPointer(1, 3, cGL_FLOAT, GL_FALSE, 0, nil) |
|
glEnableVertexAttribArray(0) |
|
glEnableVertexAttribArray(1) |
|
|
|
# Compile shaders |
|
# Vertex |
|
vertShader = glCreateShader(GL_VERTEX_SHADER) |
|
glShaderSource(vertShader, 1, vertShaderArray, nil) |
|
glCompileShader(vertShader) |
|
glGetShaderiv(vertShader, GL_COMPILE_STATUS, isCompiled.addr) |
|
|
|
# Check vertex compilation status |
|
if isCompiled == 0: |
|
echo "Vertex Shader wasn't compiled. Reason:" |
|
|
|
# Query the log size |
|
var logSize: GLint |
|
glGetShaderiv(vertShader, GL_INFO_LOG_LENGTH, logSize.addr) |
|
|
|
# Get the log itself |
|
var |
|
logStr = cast[ptr GLchar](alloc(logSize)) |
|
logLen: GLsizei |
|
|
|
glGetShaderInfoLog(vertShader, logSize.GLsizei, logLen.addr, logStr) |
|
|
|
# Print the log |
|
echo $logStr |
|
|
|
# Cleanup |
|
dealloc(logStr) |
|
else: |
|
echo "Vertex Shader compiled successfully." |
|
|
|
# Fragment |
|
fragShader = glCreateShader(GL_FRAGMENT_SHADER) |
|
glShaderSource(fragShader, 1, fragShaderArray, nil) |
|
glCompileShader(fragShader) |
|
glGetShaderiv(fragShader, GL_COMPILE_STATUS, isCompiled.addr) |
|
|
|
# Check Fragment compilation status |
|
if isCompiled == 0: |
|
echo "Fragment Shader wasn't compiled. Reason:" |
|
|
|
# Query the log size |
|
var logSize: GLint |
|
glGetShaderiv(fragShader, GL_INFO_LOG_LENGTH, logSize.addr) |
|
|
|
# Get the log itself |
|
var |
|
logStr = cast[ptr GLchar](alloc(logSize)) |
|
logLen: GLsizei |
|
|
|
glGetShaderInfoLog(fragShader, logSize.GLsizei, logLen.addr, logStr) |
|
|
|
# Print the log |
|
echo $logStr |
|
|
|
# Cleanup |
|
dealloc(logStr) |
|
else: |
|
echo "Fragment Shader compiled successfully." |
|
|
|
# Attach to a GL program |
|
shaderProgram = glCreateProgram() |
|
glAttachShader(shaderProgram, vertShader); |
|
glAttachShader(shaderProgram, fragShader); |
|
|
|
# insert locations |
|
glBindAttribLocation(shaderProgram, 0, "vertexPos"); |
|
glBindAttribLocation(shaderProgram, 0, "vertexClr"); |
|
|
|
glLinkProgram(shaderProgram); |
|
|
|
# Check for shader linking errors |
|
glGetProgramiv(shaderProgram, GL_LINK_STATUS, isLinked.addr) |
|
if isLinked == 0: |
|
echo "Wasn't able to link shaders. Reason:" |
|
|
|
# Get the log size |
|
var logSize: GLint |
|
glGetProgramiv(shaderProgram, GL_INFO_LOG_LENGTH, logSize.addr) |
|
|
|
# Get the log itself |
|
var |
|
logStr = cast[ptr GLchar](alloc(logSize)) |
|
logLen: GLsizei |
|
|
|
glGetProgramInfoLog(shaderProgram, logSize.GLsizei, logLen.addr, logStr) |
|
|
|
# Print the log |
|
echo $logStr |
|
|
|
# cleanup |
|
dealloc(logStr) |
|
else: |
|
echo "Shader Program ready!" |
|
|
|
# If everything is linked, that means we're good to go! |
|
if isLinked == 1: |
|
# Get the rotation location |
|
rotationLoc = glGetUniformLocation(shaderProgram, "rotation"); |
|
|
|
# Some other options |
|
if useAA: |
|
glEnable(GL_MULTISAMPLE) |
|
echo "Using Anti-Aliasing." |
|
|
|
# Record the time |
|
var |
|
startTicks = getTicks() |
|
curTicks = startTicks |
|
|
|
# Main loop |
|
while glfw.WindowShouldClose(window) == 0: |
|
curTicks = getTicks() |
|
|
|
# Exit on `ESC` press |
|
if glfw.GetKey(window, glfw.KEY_ESCAPE) == 1: |
|
glfw.SetWindowShouldClose(window, 1) |
|
|
|
# Clear and setup drawing |
|
glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT) |
|
glUseProgram(shaderProgram) |
|
|
|
# Update rotation |
|
let secs = (curTicks - startTicks).float / 1_000_000_000.0 |
|
if rotationSpeed != 0: |
|
rotation = 2 * PI * (secs / rotationSpeed) |
|
else: |
|
rotation = 0 |
|
|
|
# Do the drawing |
|
glBindVertexArray(vao) |
|
glUniform1f(rotationLoc, rotation) |
|
glDrawElements(GL_TRIANGLE_FAN, indices.len.GLsizei, GL_UNSIGNED_SHORT, indices.addr) |
|
|
|
# Unbind |
|
glBindVertexArray(0); |
|
glUseProgram(0); |
|
|
|
# Poll and swap |
|
glfw.PollEvents() |
|
glfw.SwapBuffers(window) |
|
|
|
|
|
# Cleanup non-GC'd stuff |
|
deallocCStringArray(vertShaderArray) |
|
deallocCStringArray(fragShaderArray) |
|
|
|
# Cleanup OpenGL Stuff |
|
glDeleteProgram(shaderProgram) |
|
glDeleteShader(vertShader) |
|
glDeleteShader(fragShader) |
|
glDeleteBuffers(1, vertexVBO.addr) |
|
glDeleteBuffers(1, colorVBO.addr) |
|
glDeleteVertexArrays(1, vao.addr) |
|
|
|
# cleanup GLFW |
|
glfw.DestroyWindow(window) |
|
glfw.Terminate() |
|
|