#+build linux
#+feature dynamic-literals
package main
import "core:fmt"
import "core:sys/posix"
import "platform/linux/X11"
import "platform/linux/shm"
import "platform/linux/xcb"
import xcberrors "platform/linux/xcb/errors"
import xcbkeysyms "platform/linux/xcb/keysyms"
import xcbshm "platform/linux/xcb/shm"
foreign import unistd "system:c"
@(default_calling_convention = "std")
foreign unistd {
usleep :: proc(usecs: u64) -> i32 ---
}
DEBUG_DRAW_TIMINGS :: #config(DEBUG_DRAW_TIMINGS, false)
// TODO: Improve with create/destroy etc.
Backbuffer :: struct {
memory: rawptr,
shm_seg_id: xcbshm.Seg,
pixmap_id: xcb.Pixmap,
shm_id: i32,
width: u16,
height: u16,
bps: u8,
pitch: u32,
}
resize_backbuffer :: proc(
backbuffer: ^Backbuffer,
connection: ^xcb.Connection,
window: xcb.Window,
width, height: u16,
) {
if backbuffer.shm_seg_id == 0 {
backbuffer.shm_seg_id = xcbshm.Seg(xcb.generate_id(connection))
backbuffer.pixmap_id = xcb.Pixmap(xcb.generate_id(connection))
} else {
shm.ctl(backbuffer.shm_id, shm.IPC_RMID, nil)
shm.dt(backbuffer.memory)
xcbshm.detach(connection, backbuffer.shm_seg_id)
xcb.free_pixmap(connection, backbuffer.pixmap_id)
}
xcb.flush(connection)
backbuffer.width = width
backbuffer.height = height
backbuffer.bps = 4
backbuffer.pitch = u32(backbuffer.width) * u32(backbuffer.height)
backbuffer.shm_id = shm.get(
shm.IPC_PRIVATE,
u32(backbuffer.bps) * backbuffer.pitch,
shm.IPC_CREAT | 0o600, // | 0o600 is file permissions
)
backbuffer.memory = shm.at(backbuffer.shm_id, nil, 0)
xcbshm.attach(connection, backbuffer.shm_seg_id, u32(backbuffer.shm_id), 0)
screen := xcb.setup_roots_iterator(xcb.get_setup(connection)).data
xcbshm.create_pixmap(
connection,
backbuffer.pixmap_id,
xcb.Drawable(window),
backbuffer.width,
backbuffer.height,
screen.root_depth,
backbuffer.shm_seg_id,
0,
)
}
get_clock_value :: #force_inline proc() -> posix.timespec {
t: posix.timespec
posix.clock_gettime(.MONOTONIC, &t)
return t
}
get_seconds_elapsed :: #force_inline proc(start, end: posix.timespec) -> f32 {
return f32(end.tv_sec - start.tv_sec) + (f32(end.tv_nsec - start.tv_nsec) / 1000000000.0)
}
input_key_translation := map[X11.Key_Code]App_Input_Key {
.Escape = .Escape,
.Num0 = .Num0,
.Num1 = .Num1,
.Num2 = .Num2,
.Num3 = .Num3,
.Num4 = .Num4,
.Num5 = .Num5,
.Num6 = .Num6,
.Num7 = .Num7,
.Num8 = .Num8,
.Num9 = .Num9,
.Left = .ArrowLeft,
.Right = .ArrowRight,
}
main :: proc() {
fmt.println("Hellope!")
window_width: u16 = 1280
window_height: u16 = 720
connection := xcb.connect(nil, nil)
defer xcb.disconnect(connection)
fmt.printf("connection %x\n", connection)
setup := xcb.get_setup(connection)
fmt.printf("setup %v\n", setup)
screen := xcb.setup_roots_iterator(setup).data
fmt.printf("screen %v\n", screen)
window := xcb.Window(xcb.generate_id(connection))
fmt.printf("window %v\n", window)
mask: u32
values: [2]u32
mask = u32(xcb.Cw.Event_Mask)
values[0] = u32(xcb.EventMask.Exposure | xcb.EventMask.Key_Press | xcb.EventMask.Key_Release)
xcb.create_window(
connection,
xcb.COPY_FROM_PARENT,
window,
screen.root,
0,
0,
window_width,
window_height,
10,
.Input_Output,
screen.root_visual,
mask,
&values[0],
)
defer xcb.destroy_window(connection, window)
// NOTE: This seems to be set to 0 until something makes us wait for a bit.
// For example: a fmt.printf. Simply moving the fmt.printf to after the
// xcb.create_gc will break it, resulting in an invalid gc. Moreover it seems
// that gcontext is overridden to 0 if xcb.create_gc is called immediately
// after xcb.generate_id. This happens only when all optimizations are turned
// off. Slows down earlier calls? Moving the entire block down closer to the
// loop start also seems to fix this. Related to window creation flush?
// gcontext := xcb.Gcontext(xcb.generate_id(connection))
// fmt.printf("gcontext: %d\n", gcontext)
// xcb.create_gc(connection, gcontext, xcb.Drawable(window), 0, nil)
// NOTE: Make sure we get the close window event (when letting decorations close the window etc).
protocol_reply := xcb.intern_atom_reply(connection, xcb.intern_atom(connection, 1, 12, "WM_PROTOCOLS"), nil)
delete_window_reply := xcb.intern_atom_reply(
connection,
xcb.intern_atom(connection, 0, 16, "WM_DELETE_WINDOW"),
nil,
)
xcb.change_property(connection, .Replace, window, protocol_reply.atom, .Atom, 32, 1, &delete_window_reply.atom)
xcb.map_window(connection, window)
xcb.flush(connection)
// NOTE: SHM support check.
if reply := xcbshm.query_version_reply(connection, xcbshm.query_version(connection), nil);
reply == nil || reply.shared_pixmaps == 0 {
fmt.printf("Shm missing?\n")
} else {
fmt.printf("Shm: %v\n", reply)
}
key_syms := xcbkeysyms.symbols_alloc(connection)
defer xcbkeysyms.symbols_free(key_syms)
backbuffers: [2]Backbuffer
backbuffer_index := 0
resize_backbuffer(&backbuffers[0], connection, window, window_width, window_height)
resize_backbuffer(&backbuffers[1], connection, window, window_width, window_height)
defer shm.ctl(backbuffers[0].shm_id, shm.IPC_RMID, nil)
defer shm.dt(backbuffers[0].memory)
defer xcbshm.detach(connection, backbuffers[0].shm_seg_id)
defer xcb.free_pixmap(connection, backbuffers[0].pixmap_id)
defer shm.ctl(backbuffers[1].shm_id, shm.IPC_RMID, nil)
defer shm.dt(backbuffers[1].memory)
defer xcbshm.detach(connection, backbuffers[1].shm_seg_id)
defer xcb.free_pixmap(connection, backbuffers[1].pixmap_id)
fmt.printf("backbuffer[0]: %+v\n", backbuffers[0])
fmt.printf("backbuffer[1]: %+v\n", backbuffers[1])
shm_completion_event := xcb.get_extension_data(connection, &xcbshm.Id).first_event + xcbshm.COMPLETION
fmt.printf("completion event: %d\n", shm_completion_event)
game_update_hz := f32(30)
target_seconds_per_frame := 1.0 / game_update_hz
last_counter := get_clock_value()
when DEBUG_DRAW_TIMINGS {
debug_frame_timings: [256]f32
debug_render_timings: [256]f32
debug_highest_timing := f32(1)
}
gcontext := xcb.Gcontext(xcb.generate_id(connection))
xcb.create_gc(connection, gcontext, xcb.Drawable(window), 0, nil)
is_running := true
ready_to_blit := true
backbuffer := backbuffers[backbuffer_index]
input: [2]App_Input
curr_input := &input[0]
prev_input := &input[1]
for is_running {
event := xcb.poll_for_event(connection)
for ; event != nil; event = xcb.poll_for_event(connection) {
switch (event.response_type & ~u8(0x80)) {
case 0:
err := (^xcb.GenericError)(event)
err_ctx: ^xcberrors.Context
xcberrors.context_new(connection, &err_ctx)
major := xcberrors.get_name_for_major_code(err_ctx, u8(err.major_code))
minor := xcberrors.get_name_for_minor_code(err_ctx, u8(err.major_code), err.minor_code)
extension: cstring
error := xcberrors.get_name_for_error(err_ctx, err.error_code, &extension)
fmt.printf(
"XCB Error: %s:%s, %s:%s, resource %u sequence %u\n",
error,
extension != nil ? extension : "no_extension",
major,
minor != nil ? minor : "no_minor",
err.resource_id,
err.sequence,
)
xcberrors.context_free(err_ctx)
case xcb.EXPOSE:
fmt.printf("XCB_EXPOSE\n")
case xcb.KEY_PRESS, xcb.KEY_RELEASE:
// fmt.printf("XCB_KEY_PRESS/RELEASE\n")
if (event.response_type == xcb.KEY_PRESS) {
evt := (^xcb.KeyPressEvent)(event)
key_sym := X11.Key_Code(xcbkeysyms.press_lookup_keysym(key_syms, evt, 0))
// fmt.printf("KEY DOWN: %v %d ?= %d\n", evt, key_sym, X11.Key_Code.Escape)
if key_sym == X11.Key_Code.Escape {
is_running = false
}
/*if translated_key, ok := input_key_translation[key_sym]; ok {
curr_input.keyboard[translated_key].down = true
}*/
/*} else {
evt := (^xcb.KeyReleaseEvent)(event)
key_sym := X11.Key_Code(xcbkeysyms.release_lookup_keysym(key_syms, evt, 0))
if translated_key, ok := input_key_translation[key_sym]; ok {
curr_input.keyboard[translated_key].down = false
}*/
}
case xcb.CLIENT_MESSAGE:
fmt.printf("XCB_CLIENT_MESSAGE\n")
evt := (^xcb.ClientMessageEvent)(event)
if evt.data.data32[0] == u32(delete_window_reply.atom) {
is_running = false
}
case shm_completion_event:
ready_to_blit = true
case:
fmt.printf("unexpected event %v\n", event)
}
xcb.free_generic_event(event)
}
/*for i in 0 ..< len(curr_input.keyboard) {
curr_input.keyboard[i].transition = false
}
for i in 0 ..< len(curr_input.keyboard) {
curr_input.keyboard[i].transition = (curr_input.keyboard[i].down != prev_input.keyboard[i].down)
}
fmt.printf("num1:\n%v\n%v\n", curr_input.keyboard[App_Input_Key.Num1], prev_input.keyboard[App_Input_Key.Num1])*/
keymap_reply := xcb.query_keymap_reply(connection, xcb.query_keymap(connection), nil)
for i in 0 ..< 256 {
state := bool((keymap_reply.keys[i / 8] >> u32(i % 8)) & 1)
key_sym := X11.Key_Code(xcbkeysyms.symbols_get_keysym(key_syms, xcb.Keycode(i), 0))
if translated_key, ok := input_key_translation[key_sym]; ok {
curr_input.keyboard[translated_key].down = state
curr_input.keyboard[translated_key].transition =
(curr_input.keyboard[translated_key].down != prev_input.keyboard[translated_key].down)
if curr_input.keyboard[translated_key].transition {
fmt.printf("key %s->%v\n", translated_key, curr_input.keyboard[translated_key])
}
}
/*if X11.Key_Code(key_sym) == X11.Key_Code.Num1 {
fmt.printf("key %s %t\n", X11.Key_Code(key_sym), state)
}*/
}
xcb.free(keymap_reply)
screen_buffer := Bitmap {
buffer = ([^]u32)(backbuffer.memory)[0:backbuffer.pitch],
width = u32(backbuffer.width),
height = u32(backbuffer.height),
}
when DEBUG_DRAW_TIMINGS {
update_and_render_timing_start := get_clock_value()
}
app_update_and_render(&screen_buffer, curr_input)
when DEBUG_DRAW_TIMINGS {
update_and_render_timing_stop := get_clock_value()
}
curr_input, prev_input = prev_input, curr_input
when DEBUG_DRAW_TIMINGS {
pix_step := 128 / debug_highest_timing
target_ms := 1000 * target_seconds_per_frame
target_ms_line := int(((target_ms * (target_ms / debug_highest_timing)) * pix_step) + 0.5)
for x := 0; x < 256; x += 1 {
frame_height: int
render_height: int
{
scaling := debug_frame_timings[x] / debug_highest_timing
frame_height = int(((debug_frame_timings[x] * scaling) * pix_step) + 0.5)
}
{
scaling := debug_render_timings[x] / debug_highest_timing
render_height = int(((debug_render_timings[x] * scaling) * pix_step) + 0.5)
}
for y := 0; y < 128; y += 1 {
i := ((127 - y) * int(screen_buffer.width)) + x
c: u32 = 0xFF000000
if y == target_ms_line {
c = 0xFFFF00FF
} else if y <= render_height {
if render_height > target_ms_line {
c = 0xFFFF0000
} else {
c = 0xFF00AA00
}
} else if y <= frame_height {
c = 0xFF0000AA
}
screen_buffer.buffer[i] = c
}
}
}
if ready_to_blit {
ready_to_blit = false
xcbshm.put_image(
connection,
xcb.Drawable(window),
gcontext,
backbuffer.width,
backbuffer.height,
0,
0,
backbuffer.width,
backbuffer.height,
0,
0,
screen.root_depth,
.z_pixmap,
1,
backbuffer.shm_seg_id,
0,
)
xcb.flush(connection)
backbuffer_index = (backbuffer_index + 1) % 2
backbuffer = backbuffers[backbuffer_index]
}
elapsed := get_seconds_elapsed(last_counter, get_clock_value())
if elapsed < target_seconds_per_frame {
sleep_mics := u64(1000000 * (target_seconds_per_frame - elapsed))
if sleep_mics > 0 {
usleep(sleep_mics)
}
for elapsed < target_seconds_per_frame {
elapsed = get_seconds_elapsed(last_counter, get_clock_value())
}
} else {
// fmt.printf("missed sleep\n")
}
end_counter := get_clock_value()
when DEBUG_DRAW_TIMINGS {
ms_per_frame := 1000.0 * get_seconds_elapsed(last_counter, end_counter)
frame_render_ms :=
1000.0 * get_seconds_elapsed(update_and_render_timing_start, update_and_render_timing_stop)
debug_highest_timing = 0
for i := 0; i < 255; i += 1 {
debug_frame_timings[i] = debug_frame_timings[i + 1]
if debug_frame_timings[i] > debug_highest_timing {
debug_highest_timing = debug_frame_timings[i]
}
debug_render_timings[i] = debug_render_timings[i + 1]
if debug_render_timings[i] > debug_highest_timing {
debug_highest_timing = debug_render_timings[i]
}
}
debug_frame_timings[255] = ms_per_frame
if ms_per_frame > debug_highest_timing {
debug_highest_timing = ms_per_frame
}
debug_render_timings[255] = frame_render_ms
if frame_render_ms > debug_highest_timing {
debug_highest_timing = frame_render_ms
}
}
last_counter = end_counter
}
}