2024-08-01 04:36:55 +02:00
|
|
|
package android_native_example
|
|
|
|
|
|
|
|
|
|
import "base:runtime"
|
|
|
|
|
|
|
|
|
|
import "shared:android"
|
|
|
|
|
|
|
|
|
|
import "vendor:egl"
|
|
|
|
|
import gl "vendor:OpenGL"
|
|
|
|
|
|
|
|
|
|
LOG :: android.__android_log_print
|
|
|
|
|
|
|
|
|
|
engine :: struct {
|
|
|
|
|
app: ^android.android_app,
|
|
|
|
|
|
|
|
|
|
active: bool,
|
|
|
|
|
display: egl.Display,
|
|
|
|
|
surface: egl.Surface,
|
|
|
|
|
ctx: egl.Context,
|
|
|
|
|
width: i32,
|
|
|
|
|
height: i32,
|
|
|
|
|
|
|
|
|
|
buffer: u32,
|
|
|
|
|
shader: u32,
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-16 23:07:32 +02:00
|
|
|
engine_init_display :: proc "contextless" (engine: ^engine) {
|
|
|
|
|
context = runtime.default_context()
|
2024-08-01 04:36:55 +02:00
|
|
|
attribs := []i32{
|
|
|
|
|
egl.SURFACE_TYPE, egl.WINDOW_BIT,
|
|
|
|
|
egl.RENDERABLE_TYPE, egl.OPENGL_ES3_BIT,
|
|
|
|
|
egl.BLUE_SIZE, 8,
|
|
|
|
|
egl.GREEN_SIZE, 8,
|
|
|
|
|
egl.RED_SIZE, 8,
|
|
|
|
|
egl.NONE,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
display := egl.GetDisplay(egl.DEFAULT_DISPLAY)
|
|
|
|
|
if display == egl.NO_DISPLAY {
|
|
|
|
|
LOG(.INFO, "NativeExampleOdin", "Naterror with eglGetDisplay")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !egl.Initialize(display, nil, nil) {
|
|
|
|
|
LOG(.INFO, "NativeExampleOdin", "error with eglInitialize")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NOTE: I think the major and minor versions don't matter here, we just want the gl package to load all available
|
|
|
|
|
// gl procs.
|
|
|
|
|
gl.load_up_to(
|
|
|
|
|
4,
|
|
|
|
|
6,
|
|
|
|
|
proc(p: rawptr, name: cstring) {(cast(^rawptr)p)^ = egl.GetProcAddress(name)}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
config: egl.Config
|
|
|
|
|
numConfigs: i32
|
|
|
|
|
if !egl.ChooseConfig(display, raw_data(attribs), &config, 1, &numConfigs) {
|
|
|
|
|
LOG(.INFO, "NativeExampleOdin", "error with eglChooseConfig")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
format: i32
|
|
|
|
|
if !egl.GetConfigAttrib(display, config, egl.NATIVE_VISUAL_ID, &format) {
|
|
|
|
|
LOG(.INFO, "NativeExampleOdin", "error with eglGetConfigAttrib")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
android.ANativeWindow_setBuffersGeometry(engine.app.window, 0, 0, format)
|
|
|
|
|
|
|
|
|
|
surface := egl.CreateWindowSurface(display, config, cast(egl.NativeWindowType)engine.app.window, nil)
|
|
|
|
|
if surface == nil {
|
|
|
|
|
LOG(.INFO, "NativeExampleOdin", "error with eglCreateWindowSurface")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ctx_attrib := []i32{ egl.CONTEXT_CLIENT_VERSION, 2, egl.NONE }
|
|
|
|
|
ctx := egl.CreateContext(display, config, nil, raw_data(ctx_attrib))
|
|
|
|
|
if ctx == nil {
|
|
|
|
|
LOG(.INFO, "NativeExampleOdin", "error with eglCreateContext")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !egl.MakeCurrent(display, surface, surface, ctx) {
|
|
|
|
|
LOG(.INFO, "NativeExampleOdin", "error with eglMakeCurrent")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOG(.INFO, "NativeExampleOdin", "GL_VENDOR = %s", gl.GetString(gl.VENDOR))
|
|
|
|
|
LOG(.INFO, "NativeExampleOdin", "GL_RENDERER = %s", gl.GetString(gl.RENDERER))
|
|
|
|
|
LOG(.INFO, "NativeExampleOdin", "GL_VERSION = %s", gl.GetString(gl.VERSION))
|
|
|
|
|
|
|
|
|
|
w: i32
|
|
|
|
|
h: i32
|
|
|
|
|
egl.QuerySurface(display, surface, egl.WIDTH, &w)
|
|
|
|
|
egl.QuerySurface(display, surface, egl.HEIGHT, &h)
|
|
|
|
|
|
|
|
|
|
LOG(.INFO, "NativeExampleOdin", "initial width: %d", w)
|
|
|
|
|
LOG(.INFO, "NativeExampleOdin", "initial height: %d", h)
|
|
|
|
|
|
|
|
|
|
gl.Viewport(0, 0, w, h)
|
|
|
|
|
|
|
|
|
|
engine.display = display
|
|
|
|
|
engine.ctx = ctx
|
|
|
|
|
engine.surface = surface
|
|
|
|
|
engine.width = w
|
|
|
|
|
engine.height = h
|
|
|
|
|
|
|
|
|
|
vasset := android.AAssetManager_open(engine.app.activity.assetManager, "vertex.glsl", .BUFFER)
|
|
|
|
|
if vasset == nil {
|
|
|
|
|
LOG(.INFO, "NativeExampleOdin", "error opening vertex.glsl")
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-04-05 03:32:37 +02:00
|
|
|
defer android.AAsset_close(vasset)
|
2024-08-01 04:36:55 +02:00
|
|
|
vsrc := cstring(android.AAsset_getBuffer(vasset))
|
|
|
|
|
vlen := i32(android.AAsset_getLength(vasset))
|
|
|
|
|
|
|
|
|
|
v := gl.CreateShader(gl.VERTEX_SHADER)
|
|
|
|
|
gl.ShaderSource(v, 1, &vsrc, &vlen)
|
|
|
|
|
gl.CompileShader(v)
|
|
|
|
|
|
|
|
|
|
fasset := android.AAssetManager_open(engine.app.activity.assetManager, "fragment.glsl", .BUFFER)
|
|
|
|
|
if fasset == nil {
|
|
|
|
|
LOG(.INFO, "NativeExampleOdin", "error opening fragment.glsl")
|
|
|
|
|
return
|
|
|
|
|
}
|
2025-04-05 03:32:37 +02:00
|
|
|
defer android.AAsset_close(fasset)
|
2024-08-01 04:36:55 +02:00
|
|
|
fsrc := cstring(android.AAsset_getBuffer(fasset))
|
|
|
|
|
flen := i32(android.AAsset_getLength(fasset))
|
|
|
|
|
|
|
|
|
|
f := gl.CreateShader(gl.FRAGMENT_SHADER)
|
|
|
|
|
gl.ShaderSource(f, 1, &fsrc, &flen)
|
|
|
|
|
gl.CompileShader(f)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
p := gl.CreateProgram()
|
|
|
|
|
gl.AttachShader(p, v)
|
|
|
|
|
gl.AttachShader(p, f)
|
|
|
|
|
|
|
|
|
|
gl.BindAttribLocation(p, 0, "vPosition")
|
|
|
|
|
gl.BindAttribLocation(p, 1, "vColor")
|
|
|
|
|
gl.LinkProgram(p)
|
|
|
|
|
|
|
|
|
|
gl.DeleteShader(v)
|
|
|
|
|
gl.DeleteShader(f)
|
|
|
|
|
gl.UseProgram(p)
|
|
|
|
|
|
|
|
|
|
buf := [?]f32 {
|
|
|
|
|
0.0, 0.5, 1, 0, 0,
|
|
|
|
|
-0.5, -0.5, 0, 1, 0,
|
|
|
|
|
0.5, -0.5, 0, 0, 1,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
b: u32
|
|
|
|
|
gl.GenBuffers(1, &b)
|
|
|
|
|
gl.BindBuffer(gl.ARRAY_BUFFER, b)
|
|
|
|
|
gl.BufferData(gl.ARRAY_BUFFER, size_of(buf), &buf, gl.STATIC_DRAW)
|
|
|
|
|
|
|
|
|
|
engine.buffer = b
|
|
|
|
|
engine.shader = p
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-16 23:07:32 +02:00
|
|
|
engine_draw_frame :: proc "contextless" (engine: ^engine) {
|
2024-08-01 04:36:55 +02:00
|
|
|
if engine.display == nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
gl.ClearColor(0.258824, 0.258824, 0.435294, 1)
|
|
|
|
|
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
|
|
|
|
|
|
|
|
|
|
gl.UseProgram(engine.shader)
|
|
|
|
|
|
|
|
|
|
gl.BindBuffer(gl.ARRAY_BUFFER, engine.buffer)
|
|
|
|
|
gl.VertexAttribPointer(0, 2, gl.FLOAT, gl.FALSE, (2+3)*size_of(f32), 0)
|
|
|
|
|
gl.VertexAttribPointer(1, 3, gl.FLOAT, gl.FALSE, (2+3)*size_of(f32), (2*size_of(f32)))
|
|
|
|
|
gl.EnableVertexAttribArray(0)
|
|
|
|
|
gl.EnableVertexAttribArray(1)
|
|
|
|
|
|
|
|
|
|
gl.DrawArrays(gl.TRIANGLES, 0, 3)
|
|
|
|
|
|
|
|
|
|
egl.SwapBuffers(engine.display, engine.surface)
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-16 23:07:32 +02:00
|
|
|
engine_term_display :: proc "contextless" (engine: ^engine) {
|
2024-08-01 04:36:55 +02:00
|
|
|
if engine.display != egl.NO_DISPLAY {
|
|
|
|
|
gl.DeleteProgram(engine.shader)
|
|
|
|
|
gl.DeleteBuffers(1, &engine.buffer)
|
|
|
|
|
|
|
|
|
|
egl.MakeCurrent(engine.display, egl.NO_SURFACE, egl.NO_SURFACE, egl.NO_CONTEXT)
|
|
|
|
|
if engine.ctx != egl.NO_CONTEXT {
|
|
|
|
|
egl.DestroyContext(engine.display, engine.ctx)
|
|
|
|
|
}
|
|
|
|
|
if engine.surface != egl.NO_SURFACE {
|
|
|
|
|
egl.DestroySurface(engine.display, engine.surface)
|
|
|
|
|
}
|
|
|
|
|
egl.Terminate(engine.display)
|
|
|
|
|
}
|
|
|
|
|
engine.active = false
|
|
|
|
|
engine.display = egl.NO_DISPLAY
|
|
|
|
|
engine.ctx = egl.NO_CONTEXT
|
|
|
|
|
engine.surface = egl.NO_SURFACE
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-16 23:07:32 +02:00
|
|
|
engine_handle_input :: proc "c" (app: ^android.android_app, event: ^android.AInputEvent) -> i32 {
|
2024-08-01 04:36:55 +02:00
|
|
|
return 0
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-16 23:07:32 +02:00
|
|
|
engine_handle_cmd :: proc "c" (app: ^android.android_app, cmd: android.AppCmd) {
|
2024-08-01 04:36:55 +02:00
|
|
|
engine := cast(^engine)app.userData
|
|
|
|
|
|
|
|
|
|
#partial switch (cmd) {
|
|
|
|
|
case .INIT_WINDOW:
|
|
|
|
|
if engine.app.window != nil {
|
|
|
|
|
LOG(.INFO, "NativeExampleOdin", "init window event")
|
|
|
|
|
engine_init_display(engine)
|
|
|
|
|
engine_draw_frame(engine)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case .TERM_WINDOW:
|
|
|
|
|
LOG(.INFO, "NativeExampleOdin", "term window event")
|
|
|
|
|
engine_term_display(engine)
|
|
|
|
|
case .WINDOW_RESIZED:
|
|
|
|
|
LOG(.INFO, "NativeExampleOdin", "window resized")
|
|
|
|
|
h := android.ANativeWindow_getHeight(app.window)
|
|
|
|
|
w := android.ANativeWindow_getWidth(app.window)
|
|
|
|
|
LOG(.INFO, "NativeExampleOdin", "w: %d", w)
|
|
|
|
|
LOG(.INFO, "NativeExampleOdin", "h: %d", h)
|
|
|
|
|
gl.Viewport(0, 0, w, h)
|
|
|
|
|
|
|
|
|
|
case .GAINED_FOCUS:
|
|
|
|
|
LOG(.INFO, "NativeExampleOdin", "gained focus event")
|
|
|
|
|
engine.active = true
|
|
|
|
|
|
|
|
|
|
case .LOST_FOCUS:
|
|
|
|
|
LOG(.INFO, "NativeExampleOdin", "lost focus event")
|
|
|
|
|
engine.active = false
|
|
|
|
|
engine_draw_frame(engine)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@export
|
|
|
|
|
android_main :: proc "contextless" (state: ^android.android_app) {
|
|
|
|
|
context = runtime.default_context()
|
|
|
|
|
|
|
|
|
|
engine: engine
|
|
|
|
|
|
|
|
|
|
state.userData = &engine
|
|
|
|
|
state.onAppCmd = engine_handle_cmd
|
|
|
|
|
state.onInputEvent = engine_handle_input
|
|
|
|
|
engine.app = state
|
|
|
|
|
|
|
|
|
|
for {
|
|
|
|
|
events: i32
|
|
|
|
|
source: ^android.android_poll_source
|
|
|
|
|
|
|
|
|
|
ident := android.ALooper_pollAll(engine.active ? 0 : -1, nil, &events, cast(^rawptr)&source)
|
|
|
|
|
for ident >= 0 {
|
|
|
|
|
if source != nil {
|
|
|
|
|
source.process(state, source)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if state.destroyRequested != 0 {
|
|
|
|
|
engine_term_display(&engine)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ident = android.ALooper_pollAll(engine.active ? 0 : -1, nil, &events, cast(^rawptr)&source)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (engine.active) {
|
|
|
|
|
engine_draw_frame(&engine)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|