OpenGL and Vulkan examples; ptr(int)/int(ptr); trailing commas in calls; drop .fn hint

- examples/opengl.fch: OpenGL 3.3 core, shaders, VAO/VBO, a spinning triangle (checked offscreen)
- examples/vulkan.fch: instance, GPUs with properties/memory/queues, logical device and queue
- ptr(n) and int(p) conversions for C APIs that pass offsets as pointers
- .ptr works on string literals; a comma after the last call argument is allowed
- a missing header included by an imported header is now named in the error
- the old .fn extension hint is gone (Finch is new, there are no old files)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Nexoniarz 2026-10-08 19:37:34 +02:00
commit 8497251dc5
22 changed files with 357 additions and 26 deletions

105
examples/opengl.fch Normal file
View file

@ -0,0 +1,105 @@
// Modern OpenGL (3.3 core): a spinning, colorful triangle drawn with shaders.
// Shows structs of numbers going to the GPU, C out-parameters with addr(), and
// pointers made from numbers with ptr(...). Needs GLFW and OpenGL (in shell.nix).
import "opengl.h"
link "glfw"
link "GL"
fn compileShader(u32 kind, str source) -> u32 {
shader := glCreateShader(kind)
src := source.ptr
glShaderSource(shader, 1, addr(src), null)
glCompileShader(shader)
i32 ok
glGetShaderiv(shader, GL_COMPILE_STATUS, addr(ok))
if ok == 0 {
log := " ".repeat(1024)
glGetShaderInfoLog(shader, 1024, null, log)
print("shader error:", str(log.ptr))
exit(1)
}
return shader
}
fn makeProgram() -> u32 {
vertex := "#version 330 core\n" +
"layout(location = 0) in vec2 pos;\n" +
"layout(location = 1) in vec3 color;\n" +
"uniform float time;\n" +
"out vec3 vColor;\n" +
"void main() {\n" +
" float c = cos(time), s = sin(time);\n" +
" gl_Position = vec4(c * pos.x - s * pos.y, s * pos.x + c * pos.y, 0.0, 1.0);\n" +
" vColor = color;\n" +
"}\n"
fragment := "#version 330 core\n" +
"in vec3 vColor;\n" +
"out vec4 outColor;\n" +
"void main() { outColor = vec4(vColor, 1.0); }\n"
vs := compileShader(GL_VERTEX_SHADER, vertex)
fs := compileShader(GL_FRAGMENT_SHADER, fragment)
program := glCreateProgram()
glAttachShader(program, vs)
glAttachShader(program, fs)
glLinkProgram(program)
glDeleteShader(vs)
glDeleteShader(fs)
return program
}
fn main() {
if glfwInit() == 0 {
print("could not start GLFW")
return
}
defer glfwTerminate()
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3)
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3)
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE)
window := glfwCreateWindow(800, 600, "Finch + OpenGL", null, null)
if window == null {
print("could not open a window (does your GPU support OpenGL 3.3?)")
return
}
glfwMakeContextCurrent(window)
glfwSwapInterval(1)
print("OpenGL", str(glGetString(GL_VERSION)), "on", str(glGetString(GL_RENDERER)))
// x, y, r, g, b for each corner
[]f32 vertices = [
0.0, 0.6, 1.0, 0.48, 0.1,
-0.6, -0.4, 0.2, 0.6, 1.0,
0.6, -0.4, 0.6, 1.0, 0.4,
]
u32 vao
u32 vbo
glGenVertexArrays(1, addr(vao))
glGenBuffers(1, addr(vbo))
glBindVertexArray(vao)
glBindBuffer(GL_ARRAY_BUFFER, vbo)
glBufferData(GL_ARRAY_BUFFER, vertices.len * 4, vertices.ptr, GL_STATIC_DRAW)
i32 stride = 5 * 4
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, stride, ptr(0))
glEnableVertexAttribArray(0)
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, stride, ptr(2 * 4))
glEnableVertexAttribArray(1)
program := makeProgram()
timeLoc := glGetUniformLocation(program, "time")
while glfwWindowShouldClose(window) == 0 {
glClearColor(0.106, 0.122, 0.165, 1.0)
glClear(GL_COLOR_BUFFER_BIT)
glUseProgram(program)
glUniform1f(timeLoc, f32(glfwGetTime()))
glDrawArrays(GL_TRIANGLES, 0, 3)
glfwSwapBuffers(window)
glfwPollEvents()
}
glDeleteBuffers(1, addr(vbo))
glDeleteVertexArrays(1, addr(vao))
glDeleteProgram(program)
}

6
examples/opengl.h Normal file
View file

@ -0,0 +1,6 @@
// Everything examples/opengl.fch needs from C. GL_GLEXT_PROTOTYPES makes <GL/glext.h>
// declare the modern (OpenGL 3+) functions, which Linux's libGL exports directly.
#define GL_GLEXT_PROTOTYPES
#include <GL/gl.h>
#include <GL/glext.h>
#include <GLFW/glfw3.h>

137
examples/vulkan.fch Normal file
View file

@ -0,0 +1,137 @@
// Vulkan from Finch: create an instance, list every GPU with its properties, memory and
// queues, then create a logical device and get a graphics queue. No window needed.
// Shows big C structs (with arrays of structs inside), out-parameters and Vulkan handles.
// Needs the Vulkan loader and headers (in shell.nix) and a Vulkan driver.
import "vulkan/vulkan.h"
link "vulkan"
fn check(i32 result, str what) {
if result != VK_SUCCESS {
print(what, "failed with VkResult", result)
exit(1)
}
}
fn version(u32 v) -> str {
return str(v >> 22) + "." + str((v >> 12) & 0x3FF) + "." + str(v & 0xFFF)
}
fn deviceType(i32 t) -> str {
if t == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU {
return "discrete GPU"
}
if t == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU {
return "integrated GPU"
}
if t == VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU {
return "virtual GPU"
}
if t == VK_PHYSICAL_DEVICE_TYPE_CPU {
return "CPU (software)"
}
return "other"
}
fn queueFlags(u32 f) -> str {
[]str parts
if f & VK_QUEUE_GRAPHICS_BIT != 0 {
parts.push("graphics")
}
if f & VK_QUEUE_COMPUTE_BIT != 0 {
parts.push("compute")
}
if f & VK_QUEUE_TRANSFER_BIT != 0 {
parts.push("transfer")
}
if f & VK_QUEUE_SPARSE_BINDING_BIT != 0 {
parts.push("sparse")
}
if f & VK_QUEUE_VIDEO_DECODE_BIT_KHR != 0 {
parts.push("video decode")
}
if f & VK_QUEUE_VIDEO_ENCODE_BIT_KHR != 0 {
parts.push("video encode")
}
if parts.len == 0 {
return "other"
}
return parts.join(", ")
}
fn main() {
app := VkApplicationInfo(
sType: VK_STRUCTURE_TYPE_APPLICATION_INFO,
pApplicationName: "Finch".ptr,
applicationVersion: 1,
pEngineName: "none".ptr,
apiVersion: 1 << 22, // Vulkan 1.0
)
info := VkInstanceCreateInfo(sType: VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, pApplicationInfo: addr(app))
ptr instance
check(vkCreateInstance(addr(info), null, addr(instance)), "vkCreateInstance")
defer vkDestroyInstance(instance, null)
u32 count
check(vkEnumeratePhysicalDevices(instance, addr(count), null), "counting GPUs")
[]ptr gpus
gpus.resize(count)
check(vkEnumeratePhysicalDevices(instance, addr(count), gpus.ptr), "listing GPUs")
print("Vulkan found", count, "device(s)")
for gpu in gpus {
VkPhysicalDeviceProperties props
vkGetPhysicalDeviceProperties(gpu, addr(props))
print()
print(str(props.deviceName.ptr))
print(" type:", deviceType(props.deviceType))
print(" Vulkan " + version(props.apiVersion) + ", driver " + str(props.driverVersion))
print(" biggest 2D image:", props.limits.maxImageDimension2D, "px")
VkPhysicalDeviceMemoryProperties mem
vkGetPhysicalDeviceMemoryProperties(gpu, addr(mem))
for h in 0..mem.memoryHeapCount {
heap := mem.memoryHeaps[h]
local := ""
if heap.flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT != 0 {
local = " (on the GPU)"
}
print(" memory heap " + str(h) + ": " + str(float(heap.size) / 1073741824.0) + " GiB" + local)
}
u32 families
vkGetPhysicalDeviceQueueFamilyProperties(gpu, addr(families), null)
[]VkQueueFamilyProperties queues
queues.resize(families)
vkGetPhysicalDeviceQueueFamilyProperties(gpu, addr(families), queues.ptr)
graphics := -1
for q in 0..queues.len {
print(" queue family " + str(q) + ": " + str(queues[q].queueCount) + " x " + queueFlags(queues[q].queueFlags))
if graphics < 0 && queues[q].queueFlags & VK_QUEUE_GRAPHICS_BIT != 0 {
graphics = q
}
}
if graphics < 0 {
continue
}
// a logical device with one graphics queue: what a renderer would start from
f32 priority = 1.0
queueInfo := VkDeviceQueueCreateInfo(
sType: VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
queueFamilyIndex: u32(graphics),
queueCount: 1,
pQueuePriorities: addr(priority),
)
deviceInfo := VkDeviceCreateInfo(
sType: VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
queueCreateInfoCount: 1,
pQueueCreateInfos: addr(queueInfo),
)
ptr device
check(vkCreateDevice(gpu, addr(deviceInfo), null, addr(device)), "vkCreateDevice")
ptr queue
vkGetDeviceQueue(device, u32(graphics), 0, addr(queue))
print(" created a device and got graphics queue", queue != null)
vkDestroyDevice(device, null)
}
}