diff --git a/odin/sdl-opengl-rendering/.gitignore b/odin/sdl-opengl-rendering/.gitignore
new file mode 100644
index 0000000..a8a0dce
--- /dev/null
+++ b/odin/sdl-opengl-rendering/.gitignore
@@ -0,0 +1 @@
+*.bin
diff --git a/odin/sdl-opengl-rendering/flake.lock b/odin/sdl-opengl-rendering/flake.lock
new file mode 100644
index 0000000..fcda77b
--- /dev/null
+++ b/odin/sdl-opengl-rendering/flake.lock
@@ -0,0 +1,61 @@
+{
+  "nodes": {
+    "flake-utils": {
+      "inputs": {
+        "systems": "systems"
+      },
+      "locked": {
+        "lastModified": 1731533236,
+        "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
+        "type": "github"
+      },
+      "original": {
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "type": "github"
+      }
+    },
+    "nixpkgs": {
+      "locked": {
+        "lastModified": 1738680400,
+        "narHash": "sha256-ooLh+XW8jfa+91F1nhf9OF7qhuA/y1ChLx6lXDNeY5U=",
+        "owner": "nixos",
+        "repo": "nixpkgs",
+        "rev": "799ba5bffed04ced7067a91798353d360788b30d",
+        "type": "github"
+      },
+      "original": {
+        "owner": "nixos",
+        "ref": "nixos-unstable",
+        "repo": "nixpkgs",
+        "type": "github"
+      }
+    },
+    "root": {
+      "inputs": {
+        "flake-utils": "flake-utils",
+        "nixpkgs": "nixpkgs"
+      }
+    },
+    "systems": {
+      "locked": {
+        "lastModified": 1681028828,
+        "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+        "owner": "nix-systems",
+        "repo": "default",
+        "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+        "type": "github"
+      },
+      "original": {
+        "owner": "nix-systems",
+        "repo": "default",
+        "type": "github"
+      }
+    }
+  },
+  "root": "root",
+  "version": 7
+}
diff --git a/odin/sdl-opengl-rendering/flake.nix b/odin/sdl-opengl-rendering/flake.nix
new file mode 100644
index 0000000..ab66b89
--- /dev/null
+++ b/odin/sdl-opengl-rendering/flake.nix
@@ -0,0 +1,87 @@
+{
+  inputs = {
+    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
+    flake-utils.url = "github:numtide/flake-utils";
+  };
+
+  outputs =
+    inputs:
+    {
+      overlays.default = final: prev: {
+        # {{{ Odin
+        odin = prev.odin.overrideAttrs (_: {
+          version = "unstable-2025-03-28";
+          src = final.fetchFromGitHub {
+            owner = "starlitcanopy";
+            repo = "Odin";
+            rev = "a1fc243f8df8b510e6de4f5e115fbfc09371cb9d";
+            sha256 = "0fb8lk47nb7ln0skjn3lyfi499q3wlnzp6w3qc4wf4s5zj43d6zh";
+          };
+        });
+        # }}}
+        # {{{ OLS
+        ols = prev.ols.overrideAttrs (_: {
+          version = "unstable-2025-03-12";
+          src = final.fetchFromGitHub {
+            owner = "DanielGavin";
+            repo = "ols";
+            rev = "1e44e3d78ad8a74ef09c7f54a6f6d3f7df517f8e";
+            sha256 = "16f7b8ijcaj5m2bdgbbl1q1mzgpgzzazrap2g17hkgy63aqq8qmf";
+          };
+
+          installPhase = ''
+            runHook preInstall
+
+            install -Dm755 ols odinfmt -t $out/bin/
+            cp      -r     builtin        $out/bin/
+            wrapProgram $out/bin/ols --set-default ODIN_ROOT ${final.odin}/share
+
+            runHook postInstall
+          '';
+        });
+        # }}}
+      };
+    }
+    // inputs.flake-utils.lib.eachSystem (with inputs.flake-utils.lib.system; [ x86_64-linux ]) (
+      system:
+      let
+        pkgs = inputs.nixpkgs.legacyPackages.${system}.extend inputs.self.overlays.default;
+        raylib = pkgs.raylib.override { platform = "SDL"; };
+        inherit (pkgs) lib;
+      in
+      {
+        packages = { inherit (pkgs) odin; };
+
+        # {{{ Shell
+        devShell = pkgs.mkShell rec {
+          nativeBuildInputs = [
+            pkgs.entr # File change detection
+            pkgs.odin # Compiler
+            pkgs.mold # Linker
+            pkgs.just # Script runner
+            pkgs.samply # Profiler
+            pkgs.ols # Language server
+            pkgs.gdb # Debugger
+            pkgs.seer # Debugger GUI
+            pkgs.valgrind # Detect memory leaks
+          ];
+
+          buildInputs = [
+            pkgs.libGL
+            pkgs.libxkbcommon
+
+            pkgs.xorg.libXi
+            pkgs.xorg.libX11
+            pkgs.xorg.libXrandr
+            pkgs.xorg.libXinerama
+            pkgs.xorg.libXcursor
+            pkgs.sdl3
+            raylib
+          ];
+
+          LD_LIBRARY_PATH = with pkgs; lib.makeLibraryPath buildInputs;
+        };
+        # }}}
+      }
+    );
+}
diff --git a/odin/sdl-opengl-rendering/src/frag.glsl b/odin/sdl-opengl-rendering/src/frag.glsl
new file mode 100644
index 0000000..e5daf9f
--- /dev/null
+++ b/odin/sdl-opengl-rendering/src/frag.glsl
@@ -0,0 +1,7 @@
+#version 140
+
+out vec4 LFragment;
+
+void main() {
+  LFragment = vec4( 1.0, 1.0, 1.0, 1.0 );
+}
diff --git a/odin/sdl-opengl-rendering/src/main.odin b/odin/sdl-opengl-rendering/src/main.odin
new file mode 100644
index 0000000..1369fa8
--- /dev/null
+++ b/odin/sdl-opengl-rendering/src/main.odin
@@ -0,0 +1,244 @@
+package visuals
+
+import "core:c"
+import "core:log"
+import "vendor:OpenGL"
+import "vendor:sdl3"
+
+State :: struct {
+	window: ^sdl3.Window,
+}
+
+init :: proc() -> (state: State, ok: bool) {
+	sdl3.Init(sdl3.InitFlags{.VIDEO}) or_return
+	sdl3.GL_SetAttribute(.CONTEXT_MAJOR_VERSION, 3) or_return
+	sdl3.GL_SetAttribute(.CONTEXT_MINOR_VERSION, 1) or_return
+	sdl3.GL_SetAttribute(.CONTEXT_PROFILE_MASK, c.int(sdl3.GL_CONTEXT_PROFILE_CORE)) or_return
+
+	window := sdl3.CreateWindow("SDL visual experiment", 640, 480, {.FULLSCREEN, .OPENGL})
+	(window != nil) or_return
+
+	gl_ctx := sdl3.GL_CreateContext(window)
+	(gl_ctx != nil) or_return
+
+	// Init GL here
+
+	return {window = window}, true
+}
+
+init_gl :: proc() -> (ok: bool) {
+	program_id := OpenGL.load_shaders_source(#load("./vert.glsl"), #load("./frag.glsl")) or_return
+	return true
+}
+
+// 				//Get vertex attribute location
+// 				gVertexPos2DLocation = glGetAttribLocation( gProgramID, "LVertexPos2D" );
+// 				if( gVertexPos2DLocation == -1 )
+// 				{
+// 					SDL_Log( "LVertexPos2D is not a valid glsl program variable!\n" );
+// 					success = false;
+// 				}
+// 				else
+// 				{
+// 					//Initialize clear color
+// 					glClearColor( 0.f, 0.f, 0.f, 1.f );
+//
+// 					//VBO data
+// 					GLfloat vertexData[] =
+// 					{
+// 						-0.5f, -0.5f,
+// 						 0.5f, -0.5f,
+// 						 0.5f,  0.5f,
+// 						-0.5f,  0.5f
+// 					};
+//
+// 					//IBO data
+// 					GLuint indexData[] = { 0, 1, 2, 3 };
+//
+// 					//Create VBO
+// 					glGenBuffers( 1, &gVBO );
+// 					glBindBuffer( GL_ARRAY_BUFFER, gVBO );
+// 					glBufferData( GL_ARRAY_BUFFER, 2 * 4 * sizeof(GLfloat), vertexData, GL_STATIC_DRAW );
+//
+// 					//Create IBO
+// 					glGenBuffers( 1, &gIBO );
+// 					glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, gIBO );
+// 					glBufferData( GL_ELEMENT_ARRAY_BUFFER, 4 * sizeof(GLuint), indexData, GL_STATIC_DRAW );
+
+// void render()
+// {
+// 	//Clear color buffer
+// 	glClear( GL_COLOR_BUFFER_BIT );
+//
+// 	//Render quad
+// 	if( gRenderQuad )
+// 	{
+// 		//Bind program
+// 		glUseProgram( gProgramID );
+//
+// 		//Enable vertex position
+// 		glEnableVertexAttribArray( gVertexPos2DLocation );
+//
+// 		//Set vertex data
+// 		glBindBuffer( GL_ARRAY_BUFFER, gVBO );
+// 		glVertexAttribPointer( gVertexPos2DLocation, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), NULL );
+//
+// 		//Set index data and render
+// 		glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, gIBO );
+// 		glDrawElements( GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, NULL );
+//
+// 		//Disable vertex position
+// 		glDisableVertexAttribArray( gVertexPos2DLocation );
+//
+// 		//Unbind program
+// 		glUseProgram( 0 );
+// 	}
+// }
+
+close :: proc(state: State) {
+	sdl3.DestroyWindow(state.window)
+	sdl3.Quit()
+}
+
+// void close()
+// {
+// 	//Deallocate program
+// 	glDeleteProgram( gProgramID );
+//
+// 	//Destroy window
+// 	SDL_DestroyWindow( gWindow );
+// 	gWindow = NULL;
+//
+// 	//Quit SDL subsystems
+// 	SDL_Quit();
+// }
+//
+// void printProgramLog( GLuint program )
+// {
+// 	//Make sure name is shader
+// 	if( glIsProgram( program ) )
+// 	{
+// 		//Program log length
+// 		int infoLogLength = 0;
+// 		int maxLength = infoLogLength;
+//
+// 		//Get info string length
+// 		glGetProgramiv( program, GL_INFO_LOG_LENGTH, &maxLength );
+//
+// 		//Allocate string
+// 		char* infoLog = new char[ maxLength ];
+//
+// 		//Get info log
+// 		glGetProgramInfoLog( program, maxLength, &infoLogLength, infoLog );
+// 		if( infoLogLength > 0 )
+// 		{
+// 			//Print Log
+// 			SDL_Log( "%s\n", infoLog );
+// 		}
+//
+// 		//Deallocate string
+// 		delete[] infoLog;
+// 	}
+// 	else
+// 	{
+// 		SDL_Log( "Name %d is not a program\n", program );
+// 	}
+// }
+//
+// void printShaderLog( GLuint shader )
+// {
+// 	//Make sure name is shader
+// 	if( glIsShader( shader ) )
+// 	{
+// 		//Shader log length
+// 		int infoLogLength = 0;
+// 		int maxLength = infoLogLength;
+//
+// 		//Get info string length
+// 		glGetShaderiv( shader, GL_INFO_LOG_LENGTH, &maxLength );
+//
+// 		//Allocate string
+// 		char* infoLog = new char[ maxLength ];
+//
+// 		//Get info log
+// 		glGetShaderInfoLog( shader, maxLength, &infoLogLength, infoLog );
+// 		if( infoLogLength > 0 )
+// 		{
+// 			//Print Log
+// 			SDL_Log( "%s\n", infoLog );
+// 		}
+//
+// 		//Deallocate string
+// 		delete[] infoLog;
+// 	}
+// 	else
+// 	{
+// 		SDL_Log( "Name %d is not a shader\n", shader );
+// 	}
+// }
+
+
+main :: proc() {
+	context.logger = log.create_console_logger()
+	log.debug("Starting")
+	state, ok := init()
+	log.assertf(ok, "Got SDL error: %v", sdl3.GetError())
+	defer close(state)
+
+	quit := false
+	log.debug("Created context")
+
+	for !quit {
+		sdl3.StartTextInput(state.window) or_break
+		event: sdl3.Event
+		for sdl3.PollEvent(&event) {
+			#partial switch event.type {
+			case .QUIT:
+				quit = true
+			}
+		}
+
+		sdl3.GL_SwapWindow(state.window)
+		sdl3.StopTextInput(state.window) or_break
+	}
+}
+// 		//Event handler
+// 		SDL_Event e;
+//
+// 		//Enable text input
+// 		SDL_StartTextInput( gWindow );
+//
+// 		//While application is running
+// 		while( !quit )
+// 		{
+// 			//Handle events on queue
+// 			while( SDL_PollEvent( &e ) != 0 )
+// 			{
+// 				//User requests quit
+// 				if( e.type == SDL_EVENT_QUIT )
+// 				{
+// 					quit = true;
+// 				}
+// 				//Handle keypress
+// 				else if( e.type == SDL_EVENT_TEXT_INPUT )
+// 				{
+// 					handleKeys( e.text.text[ 0 ] );
+// 				}
+// 			}
+//
+// 			//Render quad
+// 			render();
+//
+// 			//Update screen
+// 			SDL_GL_SwapWindow( gWindow );
+// 		}
+//
+// 		//Disable text input
+// 		SDL_StopTextInput( gWindow );
+// 	}
+//
+// 	//Free resources and close SDL
+// 	close();
+//
+// 	return 0;
+// }
diff --git a/odin/sdl-opengl-rendering/src/vert.glsl b/odin/sdl-opengl-rendering/src/vert.glsl
new file mode 100644
index 0000000..a419f7c
--- /dev/null
+++ b/odin/sdl-opengl-rendering/src/vert.glsl
@@ -0,0 +1,7 @@
+#version 140
+
+in vec2 LVertexPos2D;
+
+void main() {
+  gl_Position = vec4(LVertexPos2D.x, LVertexPos2D.y, 0, 1 );
+}