github karl-zylinski/karl2d beta-2
Beta 2: Linux & Mac support

latest releases: karl2d-jam-hotfix-1, karl2d-jam, beta-3-hotfix-1...
5 months ago

Karl2D beta 2 is now here!

This update adds Linux and Mac support.

In many cases, the support is added using low-level APIs rather than middleware libraries. This shows that Karl2D is doing well in its ambition to stay away form unnecessary library dependencies. Your games will be easier to ship when you can easily fix issues in the specific Linux, Windows or Mac platform code, without having to wade into some extra library that wraps it all.

Thank you to everyone who contributed to this beta release!

Features added since Beta 1 (since commit d47c699):

  • Linux support:
  • Mac support: Cocoa windowing and gamepad support (thanks @cpoile).
  • Simplified update API proc for updating all basic frame-dependent state, such as input and timers.
  • get_events API proc for fetching a list of events. This can be used as an alternative to polling is_key_held etc. Both are valid ways to check input!
  • init proc now has an options struct where you can specify options, replacing the old window_creation_flags enum.

Added examples:

Note

This release is primarily for visibility of new major features. The release will quickly get out-of-date as I fix things. If you want to stay up-to-date, then use the master branch instead of the files linked in this release.

This that are NOT in this beta, they will be added in the order stated:

  • Sound
  • System for cross-compiling shaders between different backends (HLSL, GLSL, GLSL ES, MSL etc)
  • Metal rendering backend for Mac (OpenGL already works)

Newsletter

I wrote a newsletter about the beta 2 release: https://news.zylinski.se/p/karl2d-beta-is-here

API diff

Expand the following section to see a full diff of karl2d.doc.odin between Beta 1 and Beta 2. This way, you can see what user-facing API surface that has changed, including comments.

Beta 1 -> Beta 2 diff of `karl2d.doc.odin`
diff --git a/karl2d.doc.odin b/karl2d.doc.odin
index 9590a0f..350d4ca 100644
--- a/karl2d.doc.odin
+++ b/karl2d.doc.odin
@@ -12,16 +12,47 @@ package karl2d
 //
 // `screen_width` and `screen_height` refer to the the resolution of the drawable area of the
 // window. The window might be slightly larger due borders and headers.
-init :: proc(screen_width: int, screen_height: int, window_title: string,
-            window_creation_flags := Window_Flags {},
-            allocator := context.allocator, loc := #caller_location) -> ^State
+init :: proc(
+	screen_width: int,
+	screen_height: int,
+	window_title: string,
+	options := Init_Options {},
+	allocator := context.allocator,
+	loc := #caller_location
+) -> ^State
+
+// Updates the internal state of the library. Call this early in the frame to make sure inputs and
+// frame times are up-to-date.
+//
+// Returns a bool that says if the player has attempted to close the window. It's up to the
+// application to decide if it wants to shut down or if it (for example) wants to show a 
+// confirmation dialogue.
+//
+// Commonly used for creating the "main loop" of a game: `for k2.update() {}`
+//
+// To get more control over how the frame is set up, you can skip calling this proc and instead use
+// the procs it calls directly:
+//
+//// for {
+////     k2.reset_frame_allocator()
+////     k2.calculate_frame_time()
+////     k2.process_events()
+////     
+////     k2.clear(k2.BLUE)
+////     k2.present()
+////     
+////     if k2.close_window_requested() {
+////         break
+////     }
+//// }
+update :: proc() -> bool
 
 // Returns true the user has pressed the close button on the window, or used a key stroke such as
 // ALT+F4 on Windows. The application can decide if it wants to shut down or if it wants to show
 // some kind of confirmation dialogue.
 //
-// Commonly used for creating the "main loop" of a game: `for !k2.shutdown_wanted {}`
-shutdown_wanted :: proc() -> bool
+// Called by `update`, but can be called manually if you need more control.
+close_window_requested :: proc() -> bool
 
 // Closes the window and cleans up Karl2D's internal state.
 shutdown :: proc()
@@ -31,62 +62,84 @@ shutdown :: proc()
 // be cleared instead.
 clear :: proc(color: Color)
 
-// Call at the start of each frame. This procedure does two main things:
-// - Fetches how long the previous frame took and how long since the program started. These values
-//   can be fetched using `get_frame_time()` and `get_time()`
-// - Clears Karl2D's internal "frame_allocator" -- that's the allocator the library uses for
-//   dynamic memory that has a lifetime of a single frame.
-new_frame :: proc()
+// The library may do some internal allocations that have the lifetime of a single frame. This
+// procedure empties that Frame Allocator.
+//
+// Called as part of `update`, but can be called manually if you need more control.
+reset_frame_allocator :: proc()
 
-// "Flips the backbuffer": Call at end of frame to make everything you've drawn appear on the screen.
+// Calculates how long the previous frame took and how it has been since the application started.
+// You can fetch the calculated values using `get_frame_time` and `get_time`.
+//
+// Called as part of `update`, but can be called manually if you need more control.
+calculate_frame_time :: proc()
+
+// Present the drawn stuff to the player. Also known as "flipping the backbuffer": Call at end of
+// frame to make everything you've drawn appear on the screen.
 //
 // When you draw using for example `draw_texture`, then that stuff is drawn to an invisible texture
 // called a "backbuffer". This makes sure that we don't see half-drawn frames. So when you are happy
 // with a frame and want to show it to the player, call this procedure.
 //
 // WebGL note: WebGL does the backbuffer flipping automatically. But you should still call this to
-// make sure that all rendering has been sent off to the GPU (it calls `draw_current_batch()`).
+// make sure that all rendering has been sent off to the GPU (as it calls `draw_current_batch()`).
 present :: proc()
 
-// Call at start or end of frame to process all events that have arrived to the window. This
-// includes keyboard, mouse, gamepad and window events.
+// Process all events that have arrived from the platform APIs. This includes keyboard, mouse,
+// gamepad and window events. This procedure processes and stores the information that procs like
+// `key_went_down` need.
 //
-// WARNING: Not calling this will make your program impossible to interact with.
+// Called by `update`, but can be called manually if you need more control.
 process_events :: proc()
 
+// Fetch a list of all events that happened this frame. Most games can use the `key_is_held`, 
+// `mouse_button_went_down` etc procedures to check input state. But if you want a list of events
+// instead, then you can use this. These events will also include things like "Window Focus" events
+// and "Window Resize" events.
+//
+// Note: Gamepad axis movement (analogue sticks and analogue triggers) are _not_ events. Those can
+// only be queried using `k2.get_gamepad_axis`.
+//
+// Warning: The returned slice is only valid during the current frame! You can make a clone of it
+// using the `slice.clone` procedure (import `core:slice`).
+get_events :: proc() -> []Event
+
 // Returns how many seconds the previous frame took. Often a tiny number such as 0.016 s.
 //
-// You must call `new_frame()` at the start of your frame in order for the frame_time to be updated.
+// This value is updated when `calculate_frame_time()` runs (which is also called by `update()`).
 get_frame_time :: proc() -> f32
 
-// Returns how many seconds has elapsed since the game started.
+// Returns how many seconds has elapsed since the game started. This is a `f64` number, giving good
+// precision when the application runs for a long time.
 //
-// You must call `new_frame()` at the start of your frame for this value to get updated.
+// This value is updated when `calculate_frame_time()` runs (which is also called by `update()`).
 get_time :: proc() -> f64
 
-// Gets the width of the drawing area within the window. The returned number is not scaled by any
-// monitor DPI scaling. You do that manually using the number returned by `get_window_scale()`.
+// Gets the width of the drawing area within the window.
 get_screen_width :: proc() -> int
 
-// Gets the height of the drawing area within the window. The returned number is not scaled by any
-// monitor DPI scaling. You do that manually using the number returned by `get_window_scale()`.
+// Gets the height of the drawing area within the window.
 get_screen_height :: proc() -> int
 
 // Moves the window.
 //
-// WebGL note: This moves the canvas within the window, which may not be what you want.
+// This does nothing for web builds.
 set_window_position :: proc(x: int, y: int)
 
-// Resize the window to a new size. If the window has the flag Resizable set, then the backbuffer
-// will also be resized.
+// Resize the window to a new size. While the user cannot resize windows with 
+// `window_mode == .Windowed_Resizable`, this procedure will those windows.
 set_window_size :: proc(width: int, height: int)
 
 // Fetch the scale of the window. This usually comes from some DPI scaling setting in the OS.
 // 1 means 100% scale, 1.5 means 150% etc.
+//
+// Karl2D does not do any automatic scaling. If you want a scaled resolution, then multiply the
+// wanted resolution by the scale and send it into `set_window_size`. You can use a camera and set
+// the zoom to the window scale in order to make things the same percieved size.
 get_window_scale :: proc() -> f32
 
-// These are the same kind of flags that you can send to `init`.
-set_window_flags :: proc(flags: Window_Flags)
+// Use to change between windowed mode, resizable windowed mode and fullscreen
+set_window_mode :: proc(window_mode: Window_Mode)
 
 // Flushes the current batch. This sends off everything to the GPU that has been queued in the
 // current batch. Normally, you do not need to do this manually. It is done automatically when these
@@ -199,6 +252,8 @@ draw_rect_vec :: proc(pos: Vec2, size: Vec2, c: Color)
 // The origin says which point the rotation rotates around. If the origin is `(0, 0)`, then the
 // rectangle rotates around the top-left corner of the rectangle. If it is `(rect.w/2, rect.h/2)`
 // then the rectangle rotates around its center.
+//
+// Rotation unit: Radians.
 draw_rect_ex :: proc(r: Rect, origin: Vec2, rot: f32, c: Color)
 
 // Draw the outline of a rectangle with a specific thickness. The outline is drawn using four
@@ -231,6 +286,8 @@ draw_texture_rect :: proc(tex: Texture, rect: Rect, pos: Vec2, tint := WHITE)
 // choice.
 //
 // Tip: Use `k2.get_texture_rect(tex)` for `src` if you want to draw the whole texture.
+//
+// Rotation unit: Radians.
 draw_texture_ex :: proc(tex: Texture, src: Rect, dst: Rect, origin: Vec2, rotation: f32, tint := WHITE)
 
 // Tells you how much space some text of a certain size will use on the screen. The font used is the
@@ -415,6 +472,9 @@ world_to_screen :: proc(pos: Vec2, camera: Camera) -> Vec2
 //    inv_offset_translate * inv_scale * inv_rot * inv_target_translate
 //
 // This is faster, since matrix inverses are expensive.
+//
+// The view matrix is a Mat4 because its easier to upload a Mat4 to the GPU. But only the upper-left
+// 3x3 matrix is actually used.
 get_camera_view_matrix :: proc(c: Camera) -> Mat4
 
 // Get the matrix that brings something in front of the camera.
@@ -564,7 +624,7 @@ Camera :: struct {
 	// target position will end up in the middle of the scren.
 	offset: Vec2,
 
-	// Rotate the camera (unit: degrees)
+	// Rotate the camera (unit: radians)
 	rotation: f32,
 
 	// Zoom the camera. A bigger value means "more zoom".
@@ -575,13 +635,15 @@ Camera :: struct {
 	zoom: f32,
 }
 
-Window_Flag :: enum {
-	// Make the window possible to resize. This will make the backbuffer automatically resize as
-	// well.
-	Resizable,
+Window_Mode :: enum {
+	Windowed,
+	Windowed_Resizable,
+	Borderless_Fullscreen,
 }
 
-Window_Flags :: bit_set[Window_Flag]
+Init_Options :: struct {
+	window_mode: Window_Mode,
+}
 
 Shader_Handle :: distinct Handle
 
@@ -644,7 +706,7 @@ Shader_Input_Type :: enum {
 }
 
 Shader_Builtin_Constant :: enum {
-	MVP,
+	View_Projection_Matrix,
 }
 
 Shader_Default_Inputs :: enum {
@@ -700,14 +762,17 @@ State :: struct {
 	allocator: runtime.Allocator,
 	frame_arena: runtime.Arena,
 	frame_allocator: runtime.Allocator,
-	win: Window_Interface,
-	window_state: rawptr,
-	rb: Render_Backend_Interface,
-	rb_state: rawptr,
+	platform: Platform_Interface,
+	platform_state: rawptr,
+	render_backend: Render_Backend_Interface,
+	render_backend_state: rawptr,
 
 	fs: fs.FontContext,
 	
-	shutdown_wanted: bool,
+	close_window_requested: bool,
+
+	// All events for this frame. Cleared when `process_events` run
+	events: [dynamic]Event,
 
 	mouse_position: Vec2,
 	mouse_delta: Vec2,
@@ -725,8 +790,6 @@ State :: struct {
 	gamepad_button_went_up: [MAX_GAMEPADS]#sparse [Gamepad_Button]bool,
 	gamepad_button_is_held: [MAX_GAMEPADS]#sparse [Gamepad_Button]bool,
 
-	window: Window_Handle,
-
 	default_font: Font,
 	fonts: [dynamic]Font_Data,
 	shape_drawing_texture: Texture_Handle,
@@ -741,9 +804,6 @@ State :: struct {
 	view_matrix: Mat4,
 	proj_matrix: Mat4,
 
-	depth: f32,
-	depth_start: f32,
-	depth_increment: f32,
 	vertex_buffer_cpu: []u8,
 	vertex_buffer_cpu_used: int,
 	default_shader: Shader,
@@ -893,6 +953,8 @@ MAX_GAMEPADS :: 4
 Gamepad_Index :: int
 
 Gamepad_Axis :: enum {
+	None,
+	
 	Left_Stick_X,
 	Left_Stick_Y,
 	Right_Stick_X,
@@ -902,6 +964,8 @@ Gamepad_Axis :: enum {
 }
 
 Gamepad_Button :: enum {
+	None,
+	
 	// DPAD buttons
 	Left_Face_Up,
 	Left_Face_Down,
@@ -926,3 +990,68 @@ Gamepad_Button :: enum {
 	Middle_Face_Middle, // PS button (not available on XBox)
 	Middle_Face_Right, // Start
 }
+
+Event :: union {
+	Event_Close_Window_Requested,
+	Event_Key_Went_Down,
+	Event_Key_Went_Up,
+	Event_Mouse_Move,
+	Event_Mouse_Wheel,
+	Event_Resize,
+	Event_Mouse_Button_Went_Down,
+	Event_Mouse_Button_Went_Up,
+	Event_Gamepad_Button_Went_Down,
+	Event_Gamepad_Button_Went_Up,
+	Event_Window_Focused,
+	Event_Window_Unfocused,
+	Event_Window_Scale_Changed,
+}
+
+Event_Key_Went_Down :: struct {
+	key: Keyboard_Key,
+}
+
+Event_Key_Went_Up :: struct {
+	key: Keyboard_Key,
+}
+
+Event_Mouse_Button_Went_Down :: struct {
+	button: Mouse_Button,
+}
+
+Event_Mouse_Button_Went_Up :: struct {
+	button: Mouse_Button,
+}
+
+Event_Gamepad_Button_Went_Down :: struct {
+	gamepad: Gamepad_Index,
+	button: Gamepad_Button,
+}
+
+Event_Gamepad_Button_Went_Up :: struct {
+	gamepad: Gamepad_Index,
+	button: Gamepad_Button,
+}
+
+Event_Close_Window_Requested :: struct {}
+
+Event_Mouse_Move :: struct {
+	position: Vec2,
+}
+
+Event_Mouse_Wheel :: struct {
+	delta: f32,
+}
+
+Event_Resize :: struct {
+	width, height: int,
+}
+
+// You can also use `k2.get_window_scale()`
+Event_Window_Scale_Changed :: struct {
+	scale: f32,
+}
+
+Event_Window_Focused :: struct {}
+
+Event_Window_Unfocused :: struct {}

Don't miss a new karl2d release

NewReleases is sending notifications on new releases.