sdl2/hellope

Code

hellope.odin ¶
301 linesSource

1/*
2	SDL2 example for Odin.
3
4	`SDL2.dll` needs to be available.
5	If `USE_SDL2_IMAGE` is enabled, `SDL2_image.dll` and `libpng16-16.dll` also need to be present.
6
7	On Windows this means placing them next to the executable.
8
9	This example code is available as a Public Domain reference under the Unlicense (https://unlicense.org/),
10	or under Odin's BSD-3 license. Choose whichever you prefer.
11*/
12
13package hellope
14
15import core_img "core:image"
16import "core:image/png"
17import "core:log"
18import "core:math"
19import "core:os"
20import "core:strings"
21import "vendor:sdl2"
22import sdl_img "vendor:sdl2/image"
23
24_ :: sdl_img // Silence -vet if it goes unused because of the USE_SDL2_IMAGE choice
25_ :: strings // Silence -vet if it goes unused because of the USE_SDL2_IMAGE choice
26
27
28USE_SDL2_IMAGE :: #config(USE_SDL2_IMAGE, false)
29
30WINDOW_TITLE  :: "Hellope World!"
31WINDOW_X      := i32(400)
32WINDOW_Y      := i32(400)
33WINDOW_WIDTH  := i32(800)
34WINDOW_HEIGHT := i32(600)
35WINDOW_FLAGS  :: sdl2.WindowFlags{.SHOWN}
36
37/*
38	If `true`, center the window using the desktop extents of the primary adapter.
39	If `false`, position at WINDOW_X, WINDOW_Y.
40*/
41CENTER_WINDOW :: true
42
43/*
44	Relative path to Odin logo
45*/
46ODIN_LOGO_PATH :: "logo-slim.png"
47
48Texture_Asset :: struct {
49	tex: ^sdl2.Texture,
50	w:   i32,
51	h:   i32,
52
53	scale: f32,
54	pivot: struct {
55		x: f32,
56		y: f32,
57	},
58}
59
60Surface :: struct {
61	surf: ^sdl2.Surface,
62	/*
63		If using `core:image/png`, `img` will hold the pointer to the `Image` returned by `png.load`.
64		Unused when `USE_SDL2_IMAGE` is enabled.
65	*/
66	img:  ^core_img.Image,
67}
68
69CTX :: struct {
70	window:        ^sdl2.Window,
71	surface:       ^sdl2.Surface,
72	renderer:      ^sdl2.Renderer,
73	textures:      [dynamic]Texture_Asset, 
74
75	should_close:  bool,
76	app_start:     f64,
77
78	frame_start:   f64,
79	frame_end:     f64,
80	frame_elapsed: f64,
81
82}
83
84ctx := CTX{}
85
86init_sdl :: proc() -> (ok: bool) {
87	if sdl_res := sdl2.Init(sdl2.INIT_VIDEO); sdl_res < 0 {
88		log.errorf("sdl2.init returned %v.", sdl_res)
89		return false
90	}
91
92	when USE_SDL2_IMAGE {
93		img_init_flags := sdl_img.INIT_PNG
94		img_res        := sdl_img.InitFlags(sdl_img.Init(img_init_flags))
95		if img_init_flags != img_res {
96			log.errorf("sdl2_image.init returned %v.", img_res)
97		}
98	}
99
100	if CENTER_WINDOW {
101		/*
102			Get desktop bounds for primary adapter
103		*/
104		bounds := sdl2.Rect{}
105		if e := sdl2.GetDisplayBounds(0, &bounds); e != 0 {
106			log.errorf("Unable to get desktop bounds.")
107			return false
108		}
109
110		WINDOW_X = ((bounds.w - bounds.x) / 2) - (WINDOW_WIDTH  / 2) + bounds.x
111		WINDOW_Y = ((bounds.h - bounds.y) / 2) - (WINDOW_HEIGHT / 2) + bounds.y
112	}
113
114	ctx.window = sdl2.CreateWindow(WINDOW_TITLE, WINDOW_X, WINDOW_Y, WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_FLAGS)
115	if ctx.window == nil {
116		log.errorf("sdl2.CreateWindow failed.")
117		return false
118	}
119
120	ctx.renderer = sdl2.CreateRenderer(ctx.window, -1, {.ACCELERATED, .PRESENTVSYNC})
121	if ctx.renderer == nil {
122		log.errorf("sdl2.CreateRenderer failed.")
123		return false
124	}
125
126	return true
127}
128
129when USE_SDL2_IMAGE { 
130	load_surface_from_image_file :: proc(image_path: string) -> (surface: ^Surface) {
131		path := strings.clone_to_cstring(image_path, context.temp_allocator)
132
133		surface = new(Surface)
134		surface.surf = sdl_img.Load(path)
135		if surface.surf == nil {
136			log.errorf("Couldn't load %v.", ODIN_LOGO_PATH)
137		}
138		return
139	}
140} else {
141	load_surface_from_image_file :: proc(image_path: string) -> (surface: ^Surface) {
142
143		/*
144			Load PNG using `core:image/png`.
145		*/
146		res_img, res_error := png.load(image_path)
147		if res_error != nil {
148			log.errorf("Couldn't load %v.", ODIN_LOGO_PATH)
149			return nil
150		}
151
152		surface = new(Surface)
153		surface.img = res_img
154
155		/*
156			Convert it into an SDL2 Surface.
157		*/
158		rmask := u32(0x000000ff)
159		gmask := u32(0x0000ff00)
160		bmask := u32(0x00ff0000)
161		amask := u32(0xff000000) if res_img.channels == 4 else u32(0x0)
162
163		depth := i32(res_img.depth) * i32(res_img.channels)
164		pitch := i32(res_img.width) * i32(res_img.channels)
165
166		surface.surf = sdl2.CreateRGBSurfaceFrom(
167			raw_data(res_img.pixels.buf),
168			i32(res_img.width), i32(res_img.height), depth, pitch,
169			rmask, gmask, bmask, amask,
170		)
171
172		return
173	}
174}
175
176destroy_surface :: proc(surface: ^Surface) {
177	assert(surface != nil)
178
179	when !USE_SDL2_IMAGE {
180		png.destroy(surface.img)
181	}
182	sdl2.FreeSurface(surface.surf)
183	free(surface)
184}
185
186init_resources :: proc() -> (ok: bool) {
187	logo_surface: ^Surface
188
189	if logo_surface = load_surface_from_image_file(ODIN_LOGO_PATH); logo_surface == nil {
190		log.errorf("Couldn't load image %v.", ODIN_LOGO_PATH)
191	}
192
193	tex := sdl2.CreateTextureFromSurface(ctx.renderer, logo_surface.surf)
194	if tex == nil {
195		log.errorf("Couldn't convert image to texture.")
196		return false
197	}
198	odin_logo := Texture_Asset{
199		tex = tex,
200		w = logo_surface.surf.w,
201		h = logo_surface.surf.h,
202
203		/*
204			By default the image is at scale 1, with the pivot in the middle of the image.
205		*/
206		scale = 1.00,
207		pivot = { 0.5, 0.5, },
208	}
209
210	destroy_surface(logo_surface)
211	append(&ctx.textures, odin_logo)
212
213	return true
214}
215
216draw :: proc() {
217	sdl2.SetRenderDrawColor(ctx.renderer, 255, 142, 27, 0xff)
218	sdl2.RenderClear(ctx.renderer)
219
220	tex := ctx.textures[0]
221	r := sdl2.Rect{
222		x = (WINDOW_WIDTH  / 2) - i32(f32(tex.w) * tex.scale * tex.pivot.x),
223		y = (WINDOW_HEIGHT / 2) - i32(f32(tex.h) * tex.scale * tex.pivot.y),
224		w = i32(f32(tex.w) * tex.scale),
225		h = i32(f32(tex.h) * tex.scale),
226	}
227	sdl2.RenderCopy(ctx.renderer, tex.tex, nil, &r)
228	sdl2.RenderPresent(ctx.renderer)
229}
230
231cleanup :: proc() {
232	defer delete(ctx.textures)
233	sdl2.DestroyWindow(ctx.window)
234	sdl2.Quit()
235}
236
237process_input :: proc() {
238	e: sdl2.Event
239
240	for sdl2.PollEvent(&e) {
241		#partial switch(e.type) {
242		case .QUIT:
243			ctx.should_close = true
244		case .KEYDOWN:
245			#partial switch(e.key.keysym.sym) {
246			case .ESCAPE:
247				ctx.should_close = true
248			}
249		}
250	}
251}
252
253update :: proc() {
254	breathe_speed := math.PI * 0.35
255	time_running  := ctx.frame_start - ctx.app_start
256	angle         := breathe_speed * time_running
257
258	ctx.textures[0].scale = 0.75 + f32(math.sin(angle)) * 0.5
259	ctx.textures[0].pivot = { 0.5 + f32(math.cos(angle)), 0.5 + f32(math.sin(angle)) }
260}
261
262loop :: proc() {
263	ctx.frame_start   = ctx.app_start
264	ctx.frame_elapsed = 0.001 // Set frame time to 1ms for the first frame to avoid problems.
265
266	for !ctx.should_close {
267		process_input()
268		update()
269		draw()
270
271		ctx.frame_end     = f64(sdl2.GetPerformanceCounter()) / f64(sdl2.GetPerformanceFrequency())
272		ctx.frame_elapsed = ctx.frame_end - ctx.frame_start
273		ctx.frame_start   = ctx.frame_end
274	}
275}
276
277main :: proc() {
278	context.logger = log.create_console_logger()
279
280	if res := init_sdl(); !res {
281		log.errorf("Initialization failed.")
282		os.exit(1)
283	}
284
285	if res := init_resources(); !res {
286		log.errorf("Couldn't initialize resources.")
287		os.exit(1)
288	}
289	defer cleanup()
290
291	/*
292		Global start time.
293	*/
294	ctx.app_start = f64(sdl2.GetPerformanceCounter()) / f64(sdl2.GetPerformanceFrequency())
295
296	loop()
297
298	now     := f64(sdl2.GetPerformanceCounter()) / f64(sdl2.GetPerformanceFrequency())
299	elapsed := now - ctx.app_start
300	log.infof("Finished in %v seconds!\n", elapsed)
301}

To Build and Run 2

Assets 4

Declarations Used 39