;; Windowing and OpenGL access are provided by SDL (import "SDL.cake" &defs-only "Licenses.cake") (c-import "stdio.h" "SDL_syswm.h" ;; Use galogen-generated header. See (generate-gl-header) &with-decls "gl46.h" "") ;; "GL/gl.h" (register-module-license "Galogen" s-apache-license-string) (def-type-alias-global gl-id (unsigned int)) (defun opengl-shader-was-compiled-sucessfully (shader gl-id helpful-name (addr (const char)) &return bool) (var success int) (var output-buffer (array 512 char)) (glGetShaderiv shader GL_COMPILE_STATUS (addr success)) (unless success (glGetShaderInfoLog shader (sizeof output-buffer) null output-buffer) (fprintf stderr "error: Shader '%s' compilation failed:\n%s\n" helpful-name output-buffer) (return false)) (return true)) (defun opengl-program-was-linked-sucessfully (program gl-id helpful-name (addr (const char)) &return bool) (var success int) (var output-buffer (array 512 char)) (glGetProgramiv program GL_LINK_STATUS (addr success)) (unless success (glGetProgramInfoLog program (sizeof output-buffer) null output-buffer) (fprintf stderr "error: Program '%s' link failed:\n%s\n" helpful-name output-buffer) (return false)) (return true)) (defun opengl-compile-link-shaders (program-id-out (addr gl-id) vertex-shader-code (addr (const char)) fragment-shader-code (addr (const char)) helpful-name (addr (const char)) &return bool) (set (deref program-id-out) (glCreateProgram)) (var vertex-shader gl-id (glCreateShader GL_VERTEX_SHADER)) (defer (glDeleteShader vertex-shader)) (scope ;; Vertex shader (glShaderSource vertex-shader 1 (addr vertex-shader-code) null) (glCompileShader vertex-shader) (unless (opengl-shader-was-compiled-sucessfully vertex-shader helpful-name) (return false))) (var fragment-shader gl-id (glCreateShader GL_FRAGMENT_SHADER)) (defer (glDeleteShader fragment-shader)) (scope ;; Fragment shader (glShaderSource fragment-shader 1 (addr fragment-shader-code) null) (glCompileShader fragment-shader) (unless (opengl-shader-was-compiled-sucessfully fragment-shader helpful-name) (return false))) (scope ;; Link vertex and fragment shader (glAttachShader (deref program-id-out) vertex-shader) (glAttachShader (deref program-id-out) fragment-shader) (glLinkProgram (deref program-id-out)) (unless (opengl-program-was-linked-sucessfully (deref program-id-out) helpful-name) (return false))) (return true)) (comptime-cond ('auto-test (defun test--opengl (&return int) (var window (addr 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 (addr (const char)) #"##version 460 core layout (location = 0) in vec3 position; layout (location = 1) in vec2 textureCoord; out vec2 TexCoord; void main() { gl_Position = vec4(position.x, position.y, position.z, 1.0); TexCoord = textureCoord; }#"#) (glShaderSource vertex-shader 1 (addr vertex-shader-code) null) (glCompileShader vertex-shader) (unless (opengl-shader-was-compiled-sucessfully vertex-shader "Test 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 (addr (const char)) #"##version 460 core out vec4 FragmentColor; in vec2 TexCoord; uniform sampler2D textureSampler; void main() { FragmentColor = texture(textureSampler, TexCoord); }#"#) (glShaderSource fragment-shader 1 (addr fragment-shader-code) null) (glCompileShader fragment-shader) (unless (opengl-shader-was-compiled-sucessfully fragment-shader "Test 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 "Test shader") (sdl-shutdown window) (return 1))) (scope ;; Clean up shaders (glDeleteShader vertex-shader) (glDeleteShader fragment-shader))) (var mesh-array-object gl-id) (var texture gl-id) (glGenVertexArrays 1 (addr mesh-array-object)) (var start-indices int 0) (var num-indices int 0) (scope (defstruct vertex-data ;; Position x float y float z float ;; Texture s float t float) (var-static vertices (array vertex-data) (array (array 0.25f 0.25f 0.25f 0.f 0.f) (array 0.75f 0.25f 0.25f 1.f 0.f) (array 0.75f 0.75f 0.25f 1.f 1.f) (array 0.25f 0.75f 0.25f 0.f 1.f))) (var-static indices (array 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 (sizeof (type vertex-data)) ;; Buffer start offset (type-cast 0 (addr 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) ;; Textures (glTexParameteri GL_TEXTURE_2D GL_TEXTURE_WRAP_S GL_REPEAT) (glTexParameteri GL_TEXTURE_2D GL_TEXTURE_WRAP_T GL_REPEAT) (glTexParameteri GL_TEXTURE_2D GL_TEXTURE_MIN_FILTER GL_NEAREST_MIPMAP_NEAREST) (glTexParameteri GL_TEXTURE_2D GL_TEXTURE_MAG_FILTER GL_NEAREST) ;; Generate a texture (var width (const int) 256) (var height (const int) 256) (var checker-image (array (* 256 256 3) (unsigned char)) (array 0)) (each-in-range height y (each-in-range width x (set (at (+ (* y width 3) (* 3 x) 0) checker-image) (? (or (= 0 (mod (/ x 64) 2)) (= 0 (mod (/ y 64) 2))) 255 0)) (set (at (+ (* y width 3) (* 3 x) 1) checker-image) 163) (set (at (+ (* y width 3) (* 3 x) 2) checker-image) 0))) (glGenTextures 1 (addr texture)) (glBindTexture GL_TEXTURE_2D texture) (glTexImage2D GL_TEXTURE_2D 0 ;; Num mips we are providing GL_RGB ;; Destination format width height 0 ;; Legacy GL_RGB ;; Source format GL_UNSIGNED_BYTE checker-image) (glGenerateMipmap GL_TEXTURE_2D) (var texture-coord-layout-location int 1) ;; Should match vertex shader layout (glVertexAttribPointer texture-coord-layout-location 2 ;; Two texture coordinates GL_FLOAT GL_FALSE ;; Normalized? (sizeof (type vertex-data)) ;; stride ;; Buffer start offset (type-cast (offsetof (type vertex-data) s) (addr void))) (glEnableVertexAttribArray texture-coord-layout-location)) (var exit-reason (addr (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")) (when (and (= (field event type) SDL_WINDOWEVENT) (= (field event window event) SDL_WINDOWEVENT_CLOSE) (= (SDL_GetWindowID window) (field event window windowID))) (set exit-reason "Window event"))) (var currentKeyStates (addr (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) (glBindTexture GL_TEXTURE_2D texture) (glBindVertexArray mesh-array-object) (glDrawElements GL_TRIANGLES num-indices GL_UNSIGNED_INT (type-cast 0 (addr 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-static-link-objects "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 (ref ModuleManager) module (addr Module) &return bool) (var galogen-source-file (addr (const char)) "Dependencies/galogen/galogen.cpp") (var galogen-executable (array 512 char)) (unless (outputFilenameFromSourceFilename (call-on c_str (field manager buildOutputDir)) "galogen" ;; Add to end of file for type. (comptime-cond ('Windows "exe") (true null)) galogen-executable (sizeof galogen-executable)) (return false)) ;; 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 (var galogen-output (array 512 char) (array 0)) (makeExecutableOutputArgument galogen-output (sizeof galogen-output) galogen-executable (call-on c_str (field manager environment compileTimeBuildCommand fileToExecute))) (run-process-sequential-or ((call-on c_str (field manager environment compileTimeBuildCommand fileToExecute)) galogen-source-file "Dependencies/galogen/third_party/tinyxml2.cpp" "/Ox" "/EHsc" galogen-output) (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 (addr (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 (array 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 (array 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 (array 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 (addr (const char)) "generate-gl-header") (var this-definition (addr 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)