GameLib is a collection of libraries for creating applications in Cakelisp.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

256 lines
10 KiB

;; Windowing and OpenGL access are provided by SDL
(import "SDL.cake")
(c-import "stdio.h"
"SDL.h"
"SDL_syswm.h"
;; Use galogen-generated header. See (generate-gl-header)
&with-decls "gl46.h") ;; "GL/gl.h"
(def-type-alias-global gl-id (unsigned int))
(defun opengl-shader-was-compiled-sucessfully (shader gl-id &return bool)
(var success int)
(var output-buffer ([] 512 char))
(glGetShaderiv shader GL_COMPILE_STATUS (addr success))
(unless success
(glGetShaderInfoLog shader (sizeof output-buffer) null output-buffer)
(fprintf stderr "error: Shader compilation failed:\n%s\n" output-buffer)
(return false))
(return true))
(defun opengl-program-was-linked-sucessfully (program gl-id &return bool)
(var success int)
(var output-buffer ([] 512 char))
(glGetProgramiv program GL_LINK_STATUS (addr success))
(unless success
(glGetProgramInfoLog program (sizeof output-buffer) null output-buffer)
(fprintf stderr "error: Program link failed:\n%s\n" output-buffer)
(return false))
(return true))
(comptime-cond
('auto-test
(defun test--opengl (&return int)
(var window (* SDL_Window) null)
(unless (sdl-initialize-for-3d (addr window) "OpenGL" 640 480) (return 1))
(SDL_GL_SetSwapInterval 1) ;; Enable vsync
;; TODO: Support resizing with window resize callback
(glViewport 0 0 640 480)
(var shader-program gl-id (glCreateProgram))
(scope ;; Prepare program
(var vertex-shader gl-id (glCreateShader GL_VERTEX_SHADER))
(scope ;; Vertex shader
;; TODO: Generate this via a new Cakelisp language/dialect feature?
(var vertex-shader-code (* (const char))
"#version 460 core\n
layout (location = 0) in vec3 position;\n
void main()\n
{\n
gl_Position = vec4(position.x, position.y, position.z, 1.0);\n
}")
(glShaderSource vertex-shader 1 (addr vertex-shader-code) null)
(glCompileShader vertex-shader)
(unless (opengl-shader-was-compiled-sucessfully vertex-shader)
(sdl-shutdown window)
(return 1)))
(var fragment-shader gl-id (glCreateShader GL_FRAGMENT_SHADER))
(scope ;; Fragment shader
;; TODO: Generate this via a new Cakelisp language/dialect feature?
(var fragment-shader-code (* (const char))
"#version 460 core\n
out vec4 FragmentColor;\n
void main()\n
{\n
FragmentColor = vec4(1.f, 0.5f, 0.2f, 1.0f);\n
}")
(glShaderSource fragment-shader 1 (addr fragment-shader-code) null)
(glCompileShader fragment-shader)
(unless (opengl-shader-was-compiled-sucessfully fragment-shader)
(sdl-shutdown window)
(return 1)))
(scope ;; Link vertex and fragment shader
(glAttachShader shader-program vertex-shader)
(glAttachShader shader-program fragment-shader)
(glLinkProgram shader-program)
(unless (opengl-program-was-linked-sucessfully shader-program)
(sdl-shutdown window)
(return 1)))
(scope ;; Clean up shaders
(glDeleteShader vertex-shader)
(glDeleteShader fragment-shader)))
(var mesh-array-object gl-id)
(glGenVertexArrays 1 (addr mesh-array-object))
(var start-indices int 0)
(var num-indices int 0)
(scope
(var-static vertices ([] float)
(array
0.25f 0.25f 0.25f
0.75f 0.25f 0.25f
0.75f 0.75f 0.25f
0.25f 0.75f 0.25f))
(var-static indices ([] int)
(array 0 1 3
1 2 3))
(set num-indices (array-size indices))
;; From this point on, configuration will be saved in the vertex array object
(glBindVertexArray mesh-array-object)
;; Make our triangle vertex array
(var vertex-buffer-id gl-id)
(glGenBuffers 1 (addr vertex-buffer-id))
(glBindBuffer GL_ARRAY_BUFFER vertex-buffer-id)
(glBufferData GL_ARRAY_BUFFER (sizeof vertices) vertices GL_STATIC_DRAW)
(var layout-location int 0) ;; Should match vertex shader layout
(glVertexAttribPointer layout-location 3 GL_FLOAT
GL_FALSE ;; Normalize?
;; Stride
(* 3 (sizeof float))
;; Buffer start offset
(type-cast 0 (* void)))
(glEnableVertexAttribArray layout-location)
;; Make our index buffer array
(var index-buffer-id gl-id)
(glGenBuffers 1 (addr index-buffer-id))
(glBindBuffer GL_ELEMENT_ARRAY_BUFFER index-buffer-id)
(glBufferData GL_ELEMENT_ARRAY_BUFFER (sizeof indices) indices GL_STATIC_DRAW))
(var exit-reason (* (const char)) null)
(while (not exit-reason)
(var event SDL_Event)
(while (SDL_PollEvent (addr event))
(when (= (field event type) SDL_QUIT)
(set exit-reason "Window event")))
(var currentKeyStates (* (const Uint8)) (SDL_GetKeyboardState null))
(when (at SDL_SCANCODE_ESCAPE currentKeyStates)
(set exit-reason "Escape pressed"))
(glClearColor 0.2f 0.2f 0.2f 1.f)
(glClear GL_COLOR_BUFFER_BIT)
(glUseProgram shader-program)
(glBindVertexArray mesh-array-object)
(glDrawElements GL_TRIANGLES num-indices GL_UNSIGNED_INT
(type-cast 0 (* void)))
(glBindVertexArray 0) ;; Unbind
(SDL_GL_SwapWindow window))
(when exit-reason
(fprintf stderr "Exiting. Reason: %s\n" exit-reason))
(sdl-shutdown window)
(return 0))))
;;
;; Building
;;
(comptime-cond
('Unix
(add-library-dependency "GL" "dl"))
('Windows
(add-linker-options "opengl32.lib")))
;; Most OpenGL loading libraries were not to my tastes. I decided on galogen
;; (http://galogen.gpfault.net/) because it's only two C++ files (not python or something else) and
;; reads directly from the Khronos spec (and I don't have to use my web browser for *****-sake)
;; See https://www.khronos.org/opengl/wiki/OpenGL_Loading_Library
(add-dependency-git-submodule clone-galogen "https://github.com/google/galogen" "Dependencies/galogen")
(defun-comptime generate-gl-header (manager (& ModuleManager) module (* Module) &return bool)
(var galogen-source-file (* (const char)) "Dependencies/galogen/galogen.cpp")
(var galogen-executable (* (const char))
(comptime-cond ('Unix "galogen") ('Windows "galogen.exe")))
;; Build galogen (GL header code generator)
(when (fileIsMoreRecentlyModified galogen-source-file galogen-executable)
(comptime-cond
('Unix
(run-process-sequential-or
((call-on c_str (field manager environment compileTimeBuildCommand fileToExecute))
galogen-source-file "Dependencies/galogen/third_party/tinyxml2.cpp"
"--std=c++11" "-O3"
"-o" galogen-executable)
(Log "error: failed to build galogen. This uses the compile-time build command\n")
(return false))
(addExecutablePermission galogen-executable))
('Windows
(run-process-sequential-or
((call-on c_str (field manager environment compileTimeBuildCommand fileToExecute))
galogen-source-file "Dependencies/galogen/third_party/tinyxml2.cpp"
"/Ox" "/EHsc")
(Log "error: failed to build galogen. This uses the compile-time build command\n")
(return false)))))
;; Use galogen to generate gl headers/source
;; TODO Use CURL or something to download the latest version from
;; https://raw.githubusercontent.com/KhronosGroup/OpenGL-Registry/master/xml/gl.xml
(var gl-specification (* (const char)) "Dependencies/galogen/third_party/gl.xml")
;; TODO: Generate the version header name to match the version SDL.cake specifies
(var gl-generated-output-path ([] 256 char) (array 0))
(unless (outputFilenameFromSourceFilename
(call-on c_str (field manager buildOutputDir))
"gl46"
null gl-generated-output-path (sizeof gl-generated-output-path))
(Log "error: failed to generate gl output filename\n")
(return false))
(var gl-generated-source-name ([] 256 char) (array 0))
(PrintfBuffer gl-generated-source-name "%s.c" gl-generated-output-path)
(when (or (fileIsMoreRecentlyModified galogen-executable
gl-generated-source-name)
(fileIsMoreRecentlyModified gl-specification
gl-generated-source-name))
(var galogen-executable-path ([] 256 char) (array 0))
(comptime-cond ('Unix (PrintfBuffer galogen-executable-path "./%s" galogen-executable))
('Windows (PrintfBuffer galogen-executable-path "%s" galogen-executable)))
(run-process-sequential-or
(galogen-executable-path
gl-specification
"--api" "gl" "--ver" "4.6" "--profile" "core"
"--filename" gl-generated-output-path)
(Log "error: failed to generate gl headers via galogen\n")
(return false)))
(scope ;; Add the generated file as a dependency
;; TODO: This needs to be cleaned up
(var gl-dependency ModuleDependency (array))
(set (field gl-dependency type) ModuleDependency_CFile)
(set (field gl-dependency name) "gl46.c")
(scope ;; Use this function as the blame token
;; TODO: Add __function__ for this
(var this-definition-name (* (const char)) "generate-gl-header")
(var this-definition (* ObjectDefinition)
(findObjectDefinition (field manager environment) this-definition-name))
(unless this-definition
(Logf "error: failed to find definition of %s to create blame token. Was it
renamed? Search for %s and replace it with the new name of the function it is defined in\n"
this-definition-name this-definition-name)
(return false))
(set (field gl-dependency blameToken) (path this-definition > definitionInvocation)))
(call-on push_back (path module > dependencies) gl-dependency))
(scope ;; Search paths for new dependency
;; Make sure Cakelisp can resolve to the file in the cache
(call-on push_back (path module > cSearchDirectories) (call-on c_str (field manager buildOutputDir)))
;; Make sure the source file can find its header
(call-on push_back (path module > cSearchDirectories) "."))
(return true))
(add-compile-time-hook-module pre-build generate-gl-header)