summaryrefslogtreecommitdiffstats
path: root/virtual-programs
diff options
context:
space:
mode:
authorOmar Rizwan <omar@omar.website>2024-05-16 17:55:10 +0000
committerGitHub <noreply@github.com>2024-05-16 17:55:10 +0000
commit4c8cd3faf49ad3cda2760ff5cb172815f62f6215 (patch)
tree8665d6bb691334acd1329bcac22b7f818520196c /virtual-programs
parentmain: Ignore folk-printed-programs (diff)
parentfolk.tcl: Fix realtime output for calibrate, remove error (diff)
downloadfolk-4c8cd3faf49ad3cda2760ff5cb172815f62f6215.tar.gz
folk-4c8cd3faf49ad3cda2760ff5cb172815f62f6215.zip
Merge pull request #139 from FolkComputer/osnr/cleanup
Cleanup (remove pi/ & play/ & laptop.tcl, mostly inline into virtual-programs/)
Diffstat (limited to 'virtual-programs')
-rw-r--r--virtual-programs/apriltags.folk78
-rw-r--r--virtual-programs/camera.folk349
-rw-r--r--virtual-programs/display.folk1556
-rw-r--r--virtual-programs/images.folk1
-rw-r--r--virtual-programs/keyboard.folk2
-rw-r--r--virtual-programs/print.folk2
6 files changed, 1973 insertions, 15 deletions
diff --git a/virtual-programs/apriltags.folk b/virtual-programs/apriltags.folk
index 548e959e..58b0c882 100644
--- a/virtual-programs/apriltags.folk
+++ b/virtual-programs/apriltags.folk
@@ -1,17 +1,85 @@
if {$::isLaptop} return
-set tagfamily "tagStandard52h13"
+set makeAprilTagDetector {{TAG_FAMILY} {
+ set detector AprilTagDetector_${TAG_FAMILY}
+ namespace eval $detector {
+ set TAG_FAMILY [uplevel {set TAG_FAMILY}]
+
+ set cc [c create]
+
+ $cc cflags -I$::env(HOME)/apriltag $::env(HOME)/apriltag/libapriltag.a
+ $cc include <apriltag.h>
+ $cc include <$TAG_FAMILY.h>
+ $cc include <math.h>
+ $cc include <assert.h>
+ $cc code {
+ apriltag_detector_t *td;
+ apriltag_family_t *tf;
+ }
+ ::defineImageType $cc
+
+ $cc proc detectInit {} void [csubst {
+ td = apriltag_detector_create();
+ tf = ${TAG_FAMILY}_create();
+ apriltag_detector_add_family_bits(td, tf, 1);
+ td->nthreads = 2;
+ }]
+ $cc proc detect {image_t gray} Tcl_Obj* {
+ assert(gray.components == 1);
+ image_u8_t im = (image_u8_t) { .width = gray.width, .height = gray.height, .stride = gray.bytesPerRow, .buf = gray.data };
+
+ zarray_t *detections = apriltag_detector_detect(td, &im);
+ int detectionCount = zarray_size(detections);
+
+ Tcl_Obj* detectionObjs[detectionCount];
+ for (int i = 0; i < detectionCount; i++) {
+ apriltag_detection_t *det;
+ zarray_get(detections, i, &det);
+
+ int size = sqrt((det->p[0][0] - det->p[1][0])*(det->p[0][0] - det->p[1][0]) + (det->p[0][1] - det->p[1][1])*(det->p[0][1] - det->p[1][1]));
+ double angle = atan2(-1 * (det->p[1][1] - det->p[0][1]), det->p[1][0] - det->p[0][0]);
+ detectionObjs[i] = Tcl_ObjPrintf("id %d center {%f %f} corners {{%f %f} {%f %f} {%f %f} {%f %f}} size %d angle %f",
+ det->id,
+ det->c[0], det->c[1],
+ det->p[0][0], det->p[0][1],
+ det->p[1][0], det->p[1][1],
+ det->p[2][0], det->p[2][1],
+ det->p[3][0], det->p[3][1],
+ size, angle);
+ }
+
+
+ zarray_destroy(detections);
+ Tcl_Obj* result = Tcl_NewListObj(detectionCount, detectionObjs);
+ return result;
+ }
+ $cc proc detectCleanup {} void [csubst {
+ ${TAG_FAMILY}_destroy(tf);
+ apriltag_detector_destroy(td);
+ }]
+
+ c loadlib $::env(HOME)/apriltag/libapriltag.so
+ $cc compile
+
+ namespace export *
+ namespace ensemble create
+ }
+
+ $detector detectInit
+ return $detector
+}}
+
+set tagFamily "tagStandard52h13"
# Plain detector. Runs on entire camera frame.
set mainDetectorProcess [Start process {
- source pi/AprilTags.tcl
+ set detector [apply $makeAprilTagDetector $tagFamily]
Wish $::thisProcess receives statements like \
[list /someone/ claims the camera frame is /grayFrame/ at /timestamp/]
Wish $::thisProcess shares statements like \
[list /someone/ claims /process/ detects tags /tags/ at /timestamp/ in time /aprilTime/]
- set detector [AprilTags new $tagfamily]
When the camera frame is /grayFrame/ at /timestamp/ {
set aprilTime [time {
set tags [$detector detect $grayFrame]
@@ -23,7 +91,7 @@ set mainDetectorProcess [Start process {
# Incremental detector. Looks at regions where there were tags in the
# old camera frame.
Start process {
- source pi/AprilTags.tcl
+ set detector [apply $makeAprilTagDetector $tagFamily]
Wish $::thisProcess receives statements like \
[list /someone/ claims the camera frame is /grayFrame/ at /timestamp/]
@@ -50,8 +118,6 @@ Start process {
}
}
- set detector [AprilTags new $tagfamily]
-
When the camera frame is /grayFrame/ at /timestamp/ & \
/process/ detects tags /prevTags/ at /something/ in time /something/ {
diff --git a/virtual-programs/camera.folk b/virtual-programs/camera.folk
index 7a4bfc44..373c904a 100644
--- a/virtual-programs/camera.folk
+++ b/virtual-programs/camera.folk
@@ -14,11 +14,358 @@ if {$::thisNode eq "folk-interact"} {
set width $::Camera::WIDTH
set height $::Camera::HEIGHT
+set makeCamera {
+ rename [c create] camc
+
+ camc include <string.h>
+ camc include <math.h>
+
+ camc include <errno.h>
+ camc include <fcntl.h>
+ camc include <sys/ioctl.h>
+ camc include <sys/mman.h>
+ camc include <asm/types.h>
+ camc include <linux/videodev2.h>
+
+ camc include <stdint.h>
+ camc include <stdlib.h>
+
+ camc include <jpeglib.h>
+
+ camc struct buffer_t {
+ uint8_t* start;
+ size_t length;
+ }
+ camc struct camera_t {
+ int fd;
+
+ uint32_t width;
+ uint32_t height;
+
+ int uses_jpeg_format;
+
+ size_t buffer_count;
+ buffer_t* buffers;
+ buffer_t head;
+ }
+
+ camc code {
+ void quit(const char* msg) {
+ fprintf(stderr, "[%s] %d: %s\n", msg, errno, strerror(errno));
+ exit(1);
+ }
+
+ int xioctl(int fd, int request, void* arg) {
+ for (int i = 0; i < 100; i++) {
+ int r = ioctl(fd, request, arg);
+ if (r != -1 || errno != EINTR) return r;
+ printf("[%x][%d] %s\n", request, i, strerror(errno));
+ }
+ return -1;
+ }
+ }
+ defineImageType camc
+
+ camc proc cameraOpen {char* device int width int height int uses_jpeg_format} camera_t* {
+ printf("device [%s]\n", device);
+ int fd = open(device, O_RDWR, 0);
+ if (fd == -1) quit("open");
+ camera_t* camera = ckalloc(sizeof (camera_t));
+ camera->fd = fd;
+ camera->width = width;
+ camera->height = height;
+ camera->uses_jpeg_format = uses_jpeg_format;
+ camera->buffer_count = 0;
+ camera->buffers = NULL;
+ camera->head.length = 0;
+ camera->head.start = NULL;
+ return camera;
+ }
+
+ camc proc cameraInit {camera_t* camera} void {
+ struct v4l2_capability cap;
+ if (xioctl(camera->fd, VIDIOC_QUERYCAP, &cap) == -1) quit("VIDIOC_QUERYCAP");
+ if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) quit("no capture");
+ if (!(cap.capabilities & V4L2_CAP_STREAMING)) quit("no streaming");
+
+ struct v4l2_format format = {0};
+ format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ format.fmt.pix.width = camera->width;
+ format.fmt.pix.height = camera->height;
+ if (camera->uses_jpeg_format) {
+ // All(?) USB webcams we've encountered use this format.
+ format.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;
+ } else {
+ // Implementing this just for Pi camera via libcamerify.
+ format.fmt.pix.pixelformat = V4L2_PIX_FMT_YUV420;
+ }
+ format.fmt.pix.field = V4L2_FIELD_NONE;
+ int ret;
+ do {
+ ret = xioctl(camera->fd, VIDIOC_S_FMT, &format);
+ } while (ret == EBUSY);
+ if (ret == -1) quit("VIDIOC_S_FMT");
+
+ if (!camera->uses_jpeg_format && format.fmt.pix.bytesperline != camera->width) {
+ fprintf(stderr, "cameraInit: interline padding not supported "
+ "(bytesperline = %u, camera->width = %u)\n",
+ format.fmt.pix.bytesperline,
+ camera->width);
+ exit(1);
+ }
+
+ struct v4l2_requestbuffers req = {0};
+ req.count = 4;
+ req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ req.memory = V4L2_MEMORY_MMAP;
+ if (xioctl(camera->fd, VIDIOC_REQBUFS, &req) == -1) quit("VIDIOC_REQBUFS");
+ camera->buffer_count = req.count;
+ camera->buffers = calloc(req.count, sizeof (buffer_t));
+
+ if (camera->uses_jpeg_format) {
+ // VIDIOC_G_PARM and VIDIOC_S_PARM are not supported by
+ // libcamerify.
+
+ struct v4l2_streamparm streamparm = {0};
+ streamparm.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ if (xioctl(camera->fd, VIDIOC_G_PARM, &streamparm) == -1) quit("VIDIOC_G_PARM");
+ if (streamparm.parm.capture.capability & V4L2_CAP_TIMEPERFRAME) {
+ int req_rate_numerator = 1;
+ int req_rate_denominator = 60;
+ streamparm.parm.capture.timeperframe.numerator = req_rate_numerator;
+ streamparm.parm.capture.timeperframe.denominator = req_rate_denominator;
+ if (xioctl(camera->fd, VIDIOC_S_PARM, &streamparm) == -1) { quit("VIDIOC_S_PARM"); }
+
+ if (streamparm.parm.capture.timeperframe.numerator != req_rate_denominator ||
+ streamparm.parm.capture.timeperframe.denominator != req_rate_numerator) {
+ fprintf(stderr,
+ "the driver changed the time per frame from "
+ "%d/%d to %d/%d\n",
+ req_rate_denominator, req_rate_numerator,
+ streamparm.parm.capture.timeperframe.numerator,
+ streamparm.parm.capture.timeperframe.denominator);
+ }
+ }
+ }
+
+ size_t buf_max = 0;
+ for (size_t i = 0; i < camera->buffer_count; i++) {
+ struct v4l2_buffer buf;
+ memset(&buf, 0, sizeof buf);
+ buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ buf.memory = V4L2_MEMORY_MMAP;
+ buf.index = i;
+ if (xioctl(camera->fd, VIDIOC_QUERYBUF, &buf) == -1)
+ quit("VIDIOC_QUERYBUF");
+ if (buf.length > buf_max) buf_max = buf.length;
+ camera->buffers[i].length = buf.length;
+ camera->buffers[i].start =
+ mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED,
+ camera->fd, buf.m.offset);
+ if (camera->buffers[i].start == MAP_FAILED) quit("mmap");
+ }
+ camera->head.start = ckalloc(buf_max);
+
+ printf("camera %d; bufcount %zu\n", camera->fd, camera->buffer_count);
+ }
+
+ camc proc cameraStart {camera_t* camera} void {
+ for (size_t i = 0; i < camera->buffer_count; i++) {
+ struct v4l2_buffer buf;
+ memset(&buf, 0, sizeof buf);
+ buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ buf.memory = V4L2_MEMORY_MMAP;
+ buf.index = i;
+ if (xioctl(camera->fd, VIDIOC_QBUF, &buf) == -1) quit("VIDIOC_QBUF");
+ printf("camera_start(%zu): %s\n", i, strerror(errno));
+ }
+
+ enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ if (xioctl(camera->fd, VIDIOC_STREAMON, &type) == -1)
+ quit("VIDIOC_STREAMON");
+ }
+
+ camc code {
+ int camera_capture(camera_t* camera) {
+ struct v4l2_buffer buf;
+ memset(&buf, 0, sizeof buf);
+ buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ buf.memory = V4L2_MEMORY_MMAP;
+ if (xioctl(camera->fd, VIDIOC_DQBUF, &buf) == -1) {
+ fprintf(stderr, "camera_capture: VIDIOC_DQBUF failed: %d: %s\n", errno, strerror(errno));
+ return 0;
+ }
+ memcpy(camera->head.start, camera->buffers[buf.index].start, buf.bytesused);
+ camera->head.length = buf.bytesused;
+ if (xioctl(camera->fd, VIDIOC_QBUF, &buf) == -1) {
+ fprintf(stderr, "camera_capture: VIDIOC_QBUF failed: %d: %s\n", errno, strerror(errno));
+ return 0;
+ }
+ return 1;
+ }
+ }
+
+ camc proc cameraFrame {camera_t* camera} int {
+ struct timeval timeout;
+ timeout.tv_sec = 1;
+ timeout.tv_usec = 0;
+ fd_set fds;
+ FD_ZERO(&fds);
+ FD_SET(camera->fd, &fds);
+ int r = select(camera->fd + 1, &fds, 0, 0, &timeout);
+ // printf("r: %d\n", r);
+ if (r == -1) quit("select");
+ if (r == 0) {
+ printf("selection failed of fd %d\n", camera->fd);
+ return 0;
+ }
+ return camera_capture(camera);
+ }
+
+ camc proc cameraDecompressRgb {camera_t* camera image_t dest} void {
+ if (!camera->uses_jpeg_format) { fprintf(stderr, "cameraDecompressRgb: non-jpeg not supported\n"); exit(1); }
+
+ struct jpeg_decompress_struct cinfo;
+ struct jpeg_error_mgr jerr;
+ cinfo.err = jpeg_std_error(&jerr);
+ jpeg_create_decompress(&cinfo);
+ jpeg_mem_src(&cinfo, camera->head.start, camera->head.length);
+ if (jpeg_read_header(&cinfo, TRUE) != 1) {
+ printf("Fail\n");
+ exit(1);
+ }
+ jpeg_start_decompress(&cinfo);
+
+ while (cinfo.output_scanline < cinfo.output_height) {
+ unsigned char *buffer_array[1];
+ buffer_array[0] = dest.data + (cinfo.output_scanline) * dest.width * cinfo.output_components;
+ jpeg_read_scanlines(&cinfo, buffer_array, 1);
+ }
+ jpeg_finish_decompress(&cinfo);
+ jpeg_destroy_decompress(&cinfo);
+ }
+ camc proc cameraDecompressGrayJpeg {camera_t* camera image_t dest} void {
+ struct jpeg_decompress_struct cinfo;
+ struct jpeg_error_mgr jerr;
+ cinfo.err = jpeg_std_error(&jerr);
+ jpeg_create_decompress(&cinfo);
+ jpeg_mem_src(&cinfo, camera->head.start, camera->head.length);
+ if (jpeg_read_header(&cinfo, TRUE) != 1) {
+ printf("Fail\n");
+ exit(1);
+ }
+ cinfo.out_color_space = JCS_GRAYSCALE;
+ jpeg_start_decompress(&cinfo);
+
+ while (cinfo.output_scanline < cinfo.output_height) {
+ unsigned char *buffer_array[1];
+ buffer_array[0] = dest.data + (cinfo.output_scanline) * dest.width * cinfo.output_components;
+ jpeg_read_scanlines(&cinfo, buffer_array, 1);
+ }
+ jpeg_finish_decompress(&cinfo);
+ jpeg_destroy_decompress(&cinfo);
+ }
+ camc proc cameraDecompressGray {camera_t* camera image_t dest} void {
+ if (camera->uses_jpeg_format) {
+ cameraDecompressGrayJpeg(camera, dest);
+ } else {
+ // Planar Y:U:V 4:2:0 format. Just copy the Y plane.
+ memcpy(dest.data, camera->head.start, camera->width * camera->height);
+ }
+ }
+ camc proc rgbToGray {image_t rgb} image_t {
+ uint8_t* gray = calloc(rgb.width * rgb.height, sizeof (uint8_t));
+ for (int y = 0; y < rgb.height; y++) {
+ for (int x = 0; x < rgb.width; x++) {
+ // we're spending 10-20% of camera time here on Pi ... ??
+
+ int i = (y * rgb.width + x) * 3;
+ uint32_t r = rgb.data[i];
+ uint32_t g = rgb.data[i + 1];
+ uint32_t b = rgb.data[i + 2];
+ // from https://mina86.com/2021/rgb-to-greyscale/
+ uint32_t yy = 3567664 * r + 11998547 * g + 1211005 * b;
+ gray[y * rgb.width + x] = ((yy + (1 << 23)) >> 24);
+ }
+ }
+ return (image_t) {
+ .width = rgb.width, .height = rgb.height,
+ .components = 1,
+ .bytesPerRow = rgb.width,
+ .data = gray
+ };
+ }
+
+ if {[namespace exists ::Heap]} {
+ camc import ::Heap::cc folkHeapAlloc as folkHeapAlloc
+ camc import ::Heap::cc folkHeapFree as folkHeapFree
+ } else {
+ camc code {
+ #define folkHeapAlloc malloc
+ #define folkHeapFree free
+ }
+ }
+ camc proc newImage {int width int height int components} image_t {
+ uint8_t* data = folkHeapAlloc(width*components*height);
+ return (image_t) {
+ .width = width,
+ .height = height,
+ .components = components,
+ .bytesPerRow = width*components,
+ .data = data
+ };
+ }
+ camc proc freeImage {image_t image} void {
+ folkHeapFree(image.data);
+ }
+
+ if {$::tcl_platform(os) eq "Darwin"} {
+ c loadlib "/opt/homebrew/lib/libjpeg.dylib"
+ } else {
+ c loadlibLd libjpeg.so
+ }
+ camc compile
+
+ variable camera
+ proc init {width height {usesJpegFormat 1}} {
+ variable camera
+ variable WIDTH; variable HEIGHT
+ set WIDTH $width; set HEIGHT $height
+ set camera [Camera::cameraOpen "/dev/video0" $width $height $usesJpegFormat]
+ Camera::cameraInit $camera
+ Camera::cameraStart $camera
+ # skip 5 frames for booting a cam
+ for {set i 0} {$i < 5} {incr i} {
+ Camera::cameraFrame $camera
+ }
+ }
+ proc frame {} {
+ variable camera
+ variable WIDTH; variable HEIGHT
+ if {![cameraFrame $camera]} {
+ error "Failed to capture from camera"
+ }
+ set image [newImage $WIDTH $HEIGHT 3]
+ cameraDecompressRgb $camera $image
+ return $image
+ }
+ proc grayFrame {} {
+ variable camera
+ variable WIDTH; variable HEIGHT
+ if {![Camera::cameraFrame $camera]} {
+ error "Failed to capture from camera"
+ }
+ set image [Camera::newImage $WIDTH $HEIGHT 1]
+ Camera::cameraDecompressGray $camera $image
+ return $image
+ }
+}
+
Start process "camera" {
Wish $::thisProcess shares statements like \
[list /someone/ claims the camera /...anything/]
- source pi/Camera.tcl
+ namespace eval Camera $makeCamera
if {[string match "gadget-*" $::thisNode]} {
Camera::init $width $height 0
diff --git a/virtual-programs/display.folk b/virtual-programs/display.folk
index 54929e89..ace7c75b 100644
--- a/virtual-programs/display.folk
+++ b/virtual-programs/display.folk
@@ -1,4 +1,1548 @@
-source "pi/Colors.tcl"
+# display.folk --
+#
+# Hardware interface with the GPU (Vulkan). Provides the ability
+# to run pixel shaders with image and numerical parameters (so you
+# can draw images, shapes, etc from Display.)
+
+set makeGpu {
+ set macos [expr {$tcl_platform(os) eq "Darwin"}]
+
+ if {!$macos} {
+ foreach renderFile [glob -nocomplain "/dev/dri/render*"] {
+ if {![file readable $renderFile]} {
+ puts stderr "Gpu: Warning: $renderFile is not readable by current user; Vulkan device may not appear.
+Try doing `sudo adduser folk render`."
+ }
+ }
+ }
+
+ if {$::isLaptop} {
+ set WIDTH [* 640 2]; set HEIGHT [* 480 2]
+ } else {
+ regexp {mode "(\d+)x(\d+)(?:-\d+)?"} [exec fbset] -> WIDTH HEIGHT
+ }
+
+ catch {rename dc ""}
+ rename [c create] dc
+ defineImageType dc
+ dc cflags -I./vendor
+ dc code {
+ #define VOLK_IMPLEMENTATION
+ #include "volk/volk.h"
+
+ static const char* VkResultToString(VkResult res) {
+ switch (res) {
+ #define CASE(x) case VK_##x: return #x;
+ CASE(SUCCESS) CASE(NOT_READY)
+ CASE(TIMEOUT) CASE(EVENT_SET)
+ CASE(EVENT_RESET) CASE(INCOMPLETE)
+ CASE(ERROR_OUT_OF_HOST_MEMORY) CASE(ERROR_OUT_OF_DEVICE_MEMORY)
+ CASE(ERROR_INITIALIZATION_FAILED) CASE(ERROR_DEVICE_LOST)
+ CASE(ERROR_MEMORY_MAP_FAILED) CASE(ERROR_LAYER_NOT_PRESENT)
+ CASE(ERROR_EXTENSION_NOT_PRESENT) CASE(ERROR_FEATURE_NOT_PRESENT)
+ CASE(ERROR_INCOMPATIBLE_DRIVER) CASE(ERROR_TOO_MANY_OBJECTS)
+ CASE(ERROR_FORMAT_NOT_SUPPORTED) CASE(ERROR_FRAGMENTED_POOL)
+ CASE(ERROR_UNKNOWN) CASE(ERROR_OUT_OF_POOL_MEMORY)
+ CASE(ERROR_INVALID_EXTERNAL_HANDLE) CASE(ERROR_FRAGMENTATION)
+ CASE(ERROR_INVALID_OPAQUE_CAPTURE_ADDRESS)
+ CASE(PIPELINE_COMPILE_REQUIRED) CASE(ERROR_SURFACE_LOST_KHR)
+ CASE(ERROR_NATIVE_WINDOW_IN_USE_KHR) CASE(SUBOPTIMAL_KHR)
+ CASE(ERROR_OUT_OF_DATE_KHR) CASE(ERROR_INCOMPATIBLE_DISPLAY_KHR)
+ CASE(ERROR_VALIDATION_FAILED_EXT) CASE(ERROR_INVALID_SHADER_NV)
+ #ifdef VK_ENABLE_BETA_EXTENSIONS
+ CASE(ERROR_IMAGE_USAGE_NOT_SUPPORTED_KHR)
+ CASE(ERROR_VIDEO_PICTURE_LAYOUT_NOT_SUPPORTED_KHR)
+ CASE(ERROR_VIDEO_PROFILE_OPERATION_NOT_SUPPORTED_KHR)
+ CASE(ERROR_VIDEO_PROFILE_FORMAT_NOT_SUPPORTED_KHR)
+ CASE(ERROR_VIDEO_PROFILE_CODEC_NOT_SUPPORTED_KHR)
+ CASE(ERROR_VIDEO_STD_VERSION_NOT_SUPPORTED_KHR)
+ #endif
+ CASE(ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT)
+ CASE(ERROR_NOT_PERMITTED_KHR)
+ CASE(ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT)
+ CASE(THREAD_IDLE_KHR) CASE(THREAD_DONE_KHR)
+ CASE(OPERATION_DEFERRED_KHR) CASE(OPERATION_NOT_DEFERRED_KHR)
+ default: return "unknown";
+ }
+ #undef CASE
+ }
+ }
+ dc include <stdlib.h>
+ if {$macos} {
+ dc cflags -I/opt/homebrew/include -L/opt/homebrew/lib
+ dc include <GLFW/glfw3.h>
+ dc cflags -lglfw
+ }
+
+ proc vktry {call} { string map {\n " "} [csubst {{
+ VkResult res = $call;
+ if (res != VK_SUCCESS) {
+ fprintf(stderr, "Failed $call: %s (%d)\n",
+ VkResultToString(res), res); exit(1);
+ }
+ }}] }
+ namespace export vktry
+
+ dc code {
+ VkInstance instance;
+ VkPhysicalDevice physicalDevice;
+ VkDevice device;
+
+ uint32_t computeQueueFamilyIndex;
+
+ VkQueue graphicsQueue;
+ VkQueue presentQueue;
+ VkQueue computeQueue;
+
+ VkRenderPass renderPass;
+
+ VkSwapchainKHR swapchain;
+ uint32_t swapchainImageCount;
+ VkFramebuffer* swapchainFramebuffers;
+ VkExtent2D swapchainExtent;
+
+ VkCommandPool commandPool;
+
+ VkCommandBuffer commandBuffer;
+ uint32_t imageIndex;
+
+ VkSemaphore imageAvailableSemaphore;
+ VkSemaphore renderFinishedSemaphore;
+ VkFence inFlightFence;
+ }
+ dc proc init {int displayIdx} void [csubst {
+ $[vktry volkInitialize()]
+ $[if {$macos} { expr {"glfwInit();"} }]
+
+ // Set up VkInstance instance:
+ {
+ VkInstanceCreateInfo createInfo = {0};
+ createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
+
+ const char* validationLayers[] = {
+ "VK_LAYER_KHRONOS_validation"
+ };
+ createInfo.enabledLayerCount = sizeof(validationLayers)/sizeof(validationLayers[0]);
+ createInfo.ppEnabledLayerNames = validationLayers;
+
+ const char* enabledExtensions[] = $[expr { $macos ? {{
+ VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME,
+ VK_KHR_SURFACE_EXTENSION_NAME,
+ "VK_EXT_metal_surface",
+ VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME,
+ }} : {{
+ // 2 extensions for non-X11/Wayland display
+ VK_KHR_SURFACE_EXTENSION_NAME,
+ VK_KHR_DISPLAY_EXTENSION_NAME,
+ VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME,
+ }} }];
+ createInfo.enabledExtensionCount = sizeof(enabledExtensions)/sizeof(enabledExtensions[0]);
+ createInfo.ppEnabledExtensionNames = enabledExtensions;
+ $[if {$macos} { expr {{
+ createInfo.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
+ }} }]
+ VkResult res = vkCreateInstance(&createInfo, NULL, &instance);
+ if (res != VK_SUCCESS) {
+ fprintf(stderr, "Failed vkCreateInstance: %s (%d)\n",
+ VkResultToString(res), res);
+ if (res == VK_ERROR_LAYER_NOT_PRESENT) {
+ fprintf(stderr, "\nIt looks like a required layer is missing.\n"
+ "Did you install `vulkan-validationlayers`?\n");
+ }
+ exit(1);
+ }
+ }
+ volkLoadInstance(instance);
+
+ // Set up VkPhysicalDevice physicalDevice
+ {
+ uint32_t physicalDeviceCount = 0;
+ vkEnumeratePhysicalDevices(instance, &physicalDeviceCount, NULL);
+ if (physicalDeviceCount == 0) {
+ fprintf(stderr, "Failed to find Vulkan physical device\n"); exit(1);
+ }
+ printf("Gpu: Found %d Vulkan devices\n", physicalDeviceCount);
+ VkPhysicalDevice physicalDevices[physicalDeviceCount];
+ vkEnumeratePhysicalDevices(instance, &physicalDeviceCount, physicalDevices);
+
+ physicalDevice = physicalDevices[0];
+ }
+
+ uint32_t graphicsQueueFamilyIndex = UINT32_MAX;
+ computeQueueFamilyIndex = UINT32_MAX; {
+ uint32_t queueFamilyCount = 0;
+ vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, NULL);
+ VkQueueFamilyProperties queueFamilies[queueFamilyCount];
+ vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, queueFamilies);
+ for (int i = 0; i < queueFamilyCount; i++) {
+ if (queueFamilies[i].queueFlags & VK_QUEUE_COMPUTE_BIT) {
+ computeQueueFamilyIndex = i;
+ }
+ if (queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
+ graphicsQueueFamilyIndex = i;
+ break;
+ }
+ }
+ if (graphicsQueueFamilyIndex == UINT32_MAX) {
+ fprintf(stderr, "Failed to find a Vulkan graphics queue family\n"); exit(1);
+ }
+ if (computeQueueFamilyIndex == UINT32_MAX) {
+ fprintf(stderr, "Failed to find a Vulkan compute queue family\n"); exit(1);
+ }
+ }
+
+ // Set up VkDevice device
+ {
+ VkDeviceQueueCreateInfo queueCreateInfo = {0};
+ queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+ queueCreateInfo.queueFamilyIndex = graphicsQueueFamilyIndex;
+ queueCreateInfo.queueCount = 1;
+ float queuePriority = 1.0f;
+ queueCreateInfo.pQueuePriorities = &queuePriority;
+
+ VkPhysicalDeviceFeatures deviceFeatures = {0};
+
+ const char *deviceExtensions[] = $[expr { $macos ? {{
+ VK_KHR_SWAPCHAIN_EXTENSION_NAME,
+ "VK_KHR_portability_subset",
+ VK_KHR_MAINTENANCE3_EXTENSION_NAME
+ }} : {{
+ VK_KHR_SWAPCHAIN_EXTENSION_NAME,
+ VK_KHR_MAINTENANCE3_EXTENSION_NAME
+ }} }];
+
+ VkDeviceCreateInfo createInfo = {0};
+ createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
+ createInfo.pQueueCreateInfos = &queueCreateInfo;
+ createInfo.queueCreateInfoCount = 1;
+ createInfo.pEnabledFeatures = &deviceFeatures;
+ createInfo.enabledLayerCount = 0;
+ createInfo.enabledExtensionCount = sizeof(deviceExtensions)/sizeof(deviceExtensions[0]);
+ createInfo.ppEnabledExtensionNames = deviceExtensions;
+
+ /* VkPhysicalDeviceDescriptorIndexingFeatures descriptorIndexingFeatures = {0}; */
+ /* descriptorIndexingFeatures.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_INDEXING_FEATURES; */
+ /* descriptorIndexingFeatures.descriptorBindingPartiallyBound = VK_TRUE; */
+ /* // TODO: Do we need more descriptor indexing features? */
+ /* createInfo.pNext = &descriptorIndexingFeatures; */
+
+ $[vktry {vkCreateDevice(physicalDevice, &createInfo, NULL, &device)}]
+ }
+
+ uint32_t propertyCount;
+ vkEnumerateInstanceLayerProperties(&propertyCount, NULL);
+ VkLayerProperties layerProperties[propertyCount];
+ vkEnumerateInstanceLayerProperties(&propertyCount, layerProperties);
+
+ // Get drawing surface.
+ VkSurfaceKHR surface;
+ $[expr { $macos ? {
+ GLFWwindow* window;
+ glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
+ window = glfwCreateWindow(640, 480, "Window Title", NULL, NULL);
+ if (glfwCreateWindowSurface(instance, window, NULL, &surface) != VK_SUCCESS) {
+ fprintf(stderr, "Gpu: Failed to create GLFW window surface\n"); exit(1);
+ }
+ } : [csubst {
+ // TODO: support multiple displays, pick best display mode
+
+ uint32_t displayCount;
+ vkGetPhysicalDeviceDisplayPropertiesKHR(physicalDevice, &displayCount, NULL);
+ printf("Gpu: Found %d displays\n", displayCount);
+ if (displayCount == 0) {
+ fprintf(stderr, "Gpu: No displays found\n"); exit(1);
+ }
+
+ VkDisplayPropertiesKHR displayProps[displayCount];
+ vkGetPhysicalDeviceDisplayPropertiesKHR(physicalDevice, &displayCount, displayProps);
+
+ uint32_t modeCount = 1; VkDisplayModePropertiesKHR modeProps;
+ vkGetDisplayModePropertiesKHR(physicalDevice, displayProps[displayIdx].display, &modeCount, &modeProps);
+
+ VkDisplaySurfaceCreateInfoKHR createInfo = {0};
+ createInfo.sType = VK_STRUCTURE_TYPE_DISPLAY_SURFACE_CREATE_INFO_KHR;
+ createInfo.displayMode = modeProps.displayMode;
+ createInfo.planeIndex = 0;
+ createInfo.transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
+ createInfo.alphaMode = VK_DISPLAY_PLANE_ALPHA_PER_PIXEL_BIT_KHR;
+ createInfo.imageExtent = (VkExtent2D) { .width = $WIDTH, .height = $HEIGHT }; // TODO: find out
+ if (vkCreateDisplayPlaneSurfaceKHR(instance, &createInfo, NULL, &surface) != VK_SUCCESS) {
+ fprintf(stderr, "Failed to create Vulkan display plane surface\n"); exit(1);
+ }
+ }] }]
+
+ uint32_t presentQueueFamilyIndex; {
+ VkBool32 presentSupport = 0;
+ vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice, graphicsQueueFamilyIndex, surface, &presentSupport);
+ if (!presentSupport) {
+ fprintf(stderr, "Vulkan graphics queue family doesn't support presenting to surface\n"); exit(1);
+ }
+ presentQueueFamilyIndex = graphicsQueueFamilyIndex;
+ }
+
+ // Figure out capabilities/format/mode of physical device for surface.
+ VkSurfaceCapabilitiesKHR capabilities;
+ VkExtent2D extent;
+ uint32_t imageCount;
+ VkSurfaceFormatKHR surfaceFormat;
+ VkPresentModeKHR presentMode; {
+ vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, surface, &capabilities);
+
+ if (capabilities.currentExtent.width != UINT32_MAX) {
+ extent = capabilities.currentExtent;
+ } else {
+ $[expr { $macos ? {
+ glfwGetFramebufferSize(window, (int*) &extent.width, (int*) &extent.height);
+ if (capabilities.minImageExtent.width > extent.width) { extent.width = capabilities.minImageExtent.width; }
+ if (capabilities.maxImageExtent.width < extent.width) { extent.width = capabilities.maxImageExtent.width; }
+ if (capabilities.minImageExtent.height > extent.height) { extent.height = capabilities.minImageExtent.height; }
+ if (capabilities.maxImageExtent.height < extent.height) { extent.height = capabilities.maxImageExtent.height; }
+ } : {} }]
+ }
+
+ imageCount = capabilities.minImageCount + 1;
+ if (capabilities.maxImageCount > 0 && imageCount > capabilities.maxImageCount) {
+ imageCount = capabilities.maxImageCount;
+ }
+
+ uint32_t formatCount;
+ vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &formatCount, NULL);
+ VkSurfaceFormatKHR formats[formatCount];
+ if (formatCount == 0) { fprintf(stderr, "No supported surface formats.\n"); exit(1); }
+ vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &formatCount, formats);
+ surfaceFormat = formats[0]; // semi-arbitrary default
+ for (int i = 0; i < formatCount; i++) {
+ if (formats[i].format == VK_FORMAT_B8G8R8A8_SRGB && formats[i].colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
+ surfaceFormat = formats[i];
+ }
+ }
+
+ uint32_t presentModeCount;
+ vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &presentModeCount, NULL);
+ VkPresentModeKHR presentModes[presentModeCount];
+ if (presentModeCount == 0) { fprintf(stderr, "No supported present modes.\n"); exit(1); }
+ vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &presentModeCount, presentModes);
+ presentMode = VK_PRESENT_MODE_FIFO_KHR; // guaranteed to be available
+ for (int i = 0; i < presentModeCount; i++) {
+ if (presentModes[i] == VK_PRESENT_MODE_MAILBOX_KHR) {
+ presentMode = presentModes[i];
+ }
+ }
+ }
+
+ // Set up VkSwapchainKHR swapchain
+ {
+ VkSwapchainCreateInfoKHR createInfo = {0};
+ createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
+ createInfo.surface = surface;
+
+ createInfo.minImageCount = imageCount;
+ createInfo.imageFormat = surfaceFormat.format;
+ createInfo.imageColorSpace = surfaceFormat.colorSpace;
+ createInfo.imageExtent = extent;
+ createInfo.imageArrayLayers = 1;
+ createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
+
+ if (graphicsQueueFamilyIndex != presentQueueFamilyIndex) {
+ fprintf(stderr, "Graphics and present queue families differ\n"); exit(1);
+ }
+ createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
+ createInfo.queueFamilyIndexCount = 0;
+ createInfo.pQueueFamilyIndices = NULL;
+
+ createInfo.preTransform = capabilities.currentTransform;
+ createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
+ createInfo.presentMode = presentMode;
+ createInfo.clipped = VK_TRUE;
+ createInfo.oldSwapchain = VK_NULL_HANDLE;
+
+ $[vktry {vkCreateSwapchainKHR(device, &createInfo, NULL, &swapchain)}]
+ }
+
+ // Set up uint32_t swapchainImageCount:
+ vkGetSwapchainImagesKHR(device, swapchain, &swapchainImageCount, NULL);
+ VkImage swapchainImages[swapchainImageCount];
+ VkFormat swapchainImageFormat;
+ // Set up VkExtent2D swapchainExtent:
+ {
+ vkGetSwapchainImagesKHR(device, swapchain, &swapchainImageCount, swapchainImages);
+ swapchainImageFormat = surfaceFormat.format;
+ swapchainExtent = extent;
+ }
+
+ VkImageView swapchainImageViews[swapchainImageCount]; {
+ for (size_t i = 0; i < swapchainImageCount; i++) {
+ VkImageViewCreateInfo createInfo = {0};
+ createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+ createInfo.image = swapchainImages[i];
+ createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
+ createInfo.format = swapchainImageFormat;
+ createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
+ createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
+ createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
+ createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
+ createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ createInfo.subresourceRange.baseMipLevel = 0;
+ createInfo.subresourceRange.levelCount = 1;
+ createInfo.subresourceRange.baseArrayLayer = 0;
+ createInfo.subresourceRange.layerCount = 1;
+ $[vktry {vkCreateImageView(device, &createInfo, NULL, &swapchainImageViews[i])}]
+ }
+ }
+
+ // Set up VkQueue graphicsQueue and VkQueue presentQueue and VkQueue computeQueue
+ {
+ vkGetDeviceQueue(device, graphicsQueueFamilyIndex, 0, &graphicsQueue);
+ presentQueue = graphicsQueue;
+ computeQueue = graphicsQueue;
+ }
+
+ // Set up VkRenderPass renderPass:
+ {
+ VkAttachmentDescription colorAttachment = {0};
+ colorAttachment.format = swapchainImageFormat;
+ colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
+ colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+ colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+ colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+ colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+ colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+ colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+
+ VkAttachmentReference colorAttachmentRef = {0};
+ colorAttachmentRef.attachment = 0;
+ colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+
+ VkSubpassDescription subpass = {0};
+ subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+ subpass.colorAttachmentCount = 1;
+ subpass.pColorAttachments = &colorAttachmentRef;
+
+ VkRenderPassCreateInfo renderPassInfo = {0};
+ renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+ renderPassInfo.attachmentCount = 1;
+ renderPassInfo.pAttachments = &colorAttachment;
+ renderPassInfo.subpassCount = 1;
+ renderPassInfo.pSubpasses = &subpass;
+
+ VkSubpassDependency dependency = {0};
+ dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
+ dependency.dstSubpass = 0;
+ dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+ dependency.srcAccessMask = 0;
+ dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+ dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+
+ renderPassInfo.dependencyCount = 1;
+ renderPassInfo.pDependencies = &dependency;
+
+ $[vktry {vkCreateRenderPass(device, &renderPassInfo, NULL, &renderPass)}]
+ }
+
+ // Set up VkFramebuffer swapchainFramebuffers[swapchainImageCount]:
+ swapchainFramebuffers = (VkFramebuffer *) ckalloc(sizeof(VkFramebuffer) * swapchainImageCount);
+ for (size_t i = 0; i < swapchainImageCount; i++) {
+ VkImageView attachments[] = { swapchainImageViews[i] };
+
+ VkFramebufferCreateInfo framebufferInfo = {0};
+ framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
+ framebufferInfo.renderPass = renderPass;
+ framebufferInfo.attachmentCount = 1;
+ framebufferInfo.pAttachments = attachments;
+ framebufferInfo.width = swapchainExtent.width;
+ framebufferInfo.height = swapchainExtent.height;
+ framebufferInfo.layers = 1;
+
+ $[vktry {vkCreateFramebuffer(device, &framebufferInfo, NULL, &swapchainFramebuffers[i])}]
+ }
+
+ // Set up VkCommandPool commandPool
+ {
+ VkCommandPoolCreateInfo poolInfo = {0};
+ poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
+ poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
+ poolInfo.queueFamilyIndex = graphicsQueueFamilyIndex;
+
+ $[vktry {vkCreateCommandPool(device, &poolInfo, NULL, &commandPool)}]
+ }
+ // Set up VkCommandBuffer commandBuffer
+ {
+ VkCommandBufferAllocateInfo allocInfo = {0};
+ allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
+ allocInfo.commandPool = commandPool;
+ allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
+ allocInfo.commandBufferCount = 1;
+
+ $[vktry {vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer)}]
+ }
+
+ {
+ VkSemaphoreCreateInfo semaphoreInfo = {0};
+ semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
+
+ VkFenceCreateInfo fenceInfo = {0};
+ fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
+ fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
+
+ $[vktry {vkCreateSemaphore(device, &semaphoreInfo, NULL, &imageAvailableSemaphore)}]
+ $[vktry {vkCreateSemaphore(device, &semaphoreInfo, NULL, &renderFinishedSemaphore)}]
+ $[vktry {vkCreateFence(device, &fenceInfo, NULL, &inFlightFence)}]
+ }
+ }]
+
+ proc defineVulkanHandleType {cc type} {
+ set cc [uplevel {namespace current}]::$cc
+ $cc argtype $type [format {
+#ifdef VK_USE_64_BIT_PTR_DEFINES
+ %s $argname; sscanf(Tcl_GetString($obj), "(%s) 0x%%p", &$argname);
+#else
+ %s $argname; sscanf(Tcl_GetString($obj), "(%s) 0x%%llx", &$argname);
+#endif
+ } $type $type $type $type]
+ # Tcl_ObjPrintf doesn't work with %lld/%llx for some reason,
+ # so we do it by hand.
+ $cc rtype $type [format {
+#ifdef VK_USE_64_BIT_PTR_DEFINES
+ $robj = Tcl_ObjPrintf("(%s) 0x%%" PRIxPTR, (uintptr_t) $rvalue);
+#else
+ {
+ char buf[100]; snprintf(buf, 100, "(%s) 0x%%llx", $rvalue);
+ $robj = Tcl_NewStringObj(buf, -1);
+ }
+#endif
+ } $type $type]
+ }
+
+ # Shader compilation:
+
+ defineVulkanHandleType dc VkShaderModule
+ dc proc createShaderModule {Tcl_Obj* codeObj} VkShaderModule [csubst {
+ int codeObjc; Tcl_Obj** codeObjv;
+ Tcl_ListObjGetElements(NULL, codeObj, &codeObjc, &codeObjv);
+ uint32_t code[codeObjc];
+ for (int i = 0; i < codeObjc; i++) {
+ Tcl_GetIntFromObj(NULL, codeObjv[i], (int32_t *)&code[i]);
+ }
+
+ VkShaderModuleCreateInfo createInfo = {0};
+ createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
+ createInfo.codeSize = codeObjc * sizeof(code[0]);
+ createInfo.pCode = code;
+
+ VkShaderModule shaderModule;
+ $[vktry {vkCreateShaderModule(device, &createInfo, NULL, &shaderModule)}]
+ return shaderModule;
+ }]
+
+ # Pipeline creation:
+ defineVulkanHandleType dc VkPipeline
+ defineVulkanHandleType dc VkPipelineLayout
+ defineVulkanHandleType dc VkDescriptorSet
+ defineVulkanHandleType dc VkDescriptorSetLayout
+ dc typedef uint64_t VkDeviceSize
+ dc argtype VkDescriptorType { int $argname; __ENSURE_OK(Tcl_GetIntFromObj(interp, $obj, &$argname)); }
+ dc rtype VkDescriptorType { $robj = Tcl_NewIntObj($rvalue); }
+ dc struct Pipeline {
+ VkPipeline pipeline;
+ VkPipelineLayout pipelineLayout;
+
+ int id;
+ size_t pushConstantsSize;
+ }
+ dc proc createPipeline {int id
+ VkShaderModule vertShaderModule
+ VkShaderModule fragShaderModule
+ size_t pushConstantsSize} Pipeline [csubst {
+ VkPipelineShaderStageCreateInfo shaderStages[2]; {
+ VkPipelineShaderStageCreateInfo vertShaderStageInfo = {0};
+ vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+ vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
+ vertShaderStageInfo.module = vertShaderModule;
+ vertShaderStageInfo.pName = "main";
+
+ VkPipelineShaderStageCreateInfo fragShaderStageInfo = {0};
+ fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+ fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
+ fragShaderStageInfo.module = fragShaderModule;
+ fragShaderStageInfo.pName = "main";
+
+ shaderStages[0] = vertShaderStageInfo;
+ shaderStages[1] = fragShaderStageInfo;
+ }
+
+ VkPipelineVertexInputStateCreateInfo vertexInputInfo = {0}; {
+ vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
+ vertexInputInfo.vertexBindingDescriptionCount = 0;
+ vertexInputInfo.vertexAttributeDescriptionCount = 0;
+ }
+
+ VkPipelineInputAssemblyStateCreateInfo inputAssembly = {0}; {
+ inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
+ // We're just going to draw a quad (4 vertices -> first 3
+ // vertices are top-left triangle, last 3 vertices are
+ // bottom-right triangle).
+ inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
+ inputAssembly.primitiveRestartEnable = VK_FALSE;
+ }
+
+ VkViewport viewport = {0}; {
+ viewport.x = 0.0f;
+ viewport.y = 0.0f;
+ viewport.width = (float) swapchainExtent.width;
+ viewport.height = (float) swapchainExtent.height;
+ viewport.minDepth = 0.0f;
+ viewport.maxDepth = 1.0f;
+ }
+ VkRect2D scissor = {0}; {
+ scissor.offset = (VkOffset2D) {0, 0};
+ scissor.extent = swapchainExtent;
+ }
+ VkPipelineViewportStateCreateInfo viewportState = {0};
+ viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
+ viewportState.viewportCount = 1;
+ viewportState.pViewports = &viewport;
+ viewportState.scissorCount = 1;
+ viewportState.pScissors = &scissor;
+
+ VkPipelineRasterizationStateCreateInfo rasterizer = {0};
+ rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
+ rasterizer.depthClampEnable = VK_FALSE;
+ rasterizer.rasterizerDiscardEnable = VK_FALSE;
+ rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
+ rasterizer.lineWidth = 1.0f;
+ rasterizer.cullMode = VK_CULL_MODE_BACK_BIT;
+ rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;
+ rasterizer.depthBiasEnable = VK_FALSE;
+
+ VkPipelineMultisampleStateCreateInfo multisampling = {0};
+ multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
+ multisampling.sampleShadingEnable = VK_FALSE;
+ multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
+
+ VkPipelineColorBlendAttachmentState colorBlendAttachment = {0};
+ colorBlendAttachment.colorWriteMask =
+ VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT |
+ VK_COLOR_COMPONENT_A_BIT;
+ colorBlendAttachment.blendEnable = VK_TRUE;
+ colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
+ colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
+ colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
+ colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
+ colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
+ colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
+
+ VkPipelineColorBlendStateCreateInfo colorBlending = {0};
+ colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
+ colorBlending.logicOpEnable = VK_FALSE;
+ colorBlending.logicOp = VK_LOGIC_OP_COPY; // Optional
+ colorBlending.attachmentCount = 1;
+ colorBlending.pAttachments = &colorBlendAttachment;
+
+ VkPipelineLayout pipelineLayout; {
+ VkPipelineLayoutCreateInfo pipelineLayoutInfo = {0};
+ pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
+
+ pipelineLayoutInfo.pSetLayouts = &imageDescriptorSetLayout;
+ pipelineLayoutInfo.setLayoutCount = 1;
+
+ // We configure all pipelines with push constants size =
+ // 128 (the maximum), no matter what actual push constants
+ // they take; this is so that pipelines are all
+ // layout-compatible so we can reuse descriptor set
+ // between pipelines without needing to rebind it.
+ {
+ VkPushConstantRange pushConstantRange = {0};
+ pushConstantRange.offset = 0;
+ pushConstantRange.size = 128;
+ pushConstantRange.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
+
+ pipelineLayoutInfo.pPushConstantRanges = &pushConstantRange;
+ pipelineLayoutInfo.pushConstantRangeCount = 1;
+ }
+
+ $[vktry {vkCreatePipelineLayout(device, &pipelineLayoutInfo, NULL, &pipelineLayout)}]
+ }
+
+ VkPipeline pipeline; {
+ VkGraphicsPipelineCreateInfo pipelineInfo = {0};
+ pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
+ pipelineInfo.stageCount = 2;
+ pipelineInfo.pStages = shaderStages;
+ pipelineInfo.pVertexInputState = &vertexInputInfo;
+ pipelineInfo.pInputAssemblyState = &inputAssembly;
+ pipelineInfo.pViewportState = &viewportState;
+ pipelineInfo.pRasterizationState = &rasterizer;
+ pipelineInfo.pMultisampleState = &multisampling;
+ pipelineInfo.pDepthStencilState = NULL;
+ pipelineInfo.pColorBlendState = &colorBlending;
+ pipelineInfo.pDynamicState = NULL;
+
+ pipelineInfo.layout = pipelineLayout;
+
+ pipelineInfo.renderPass = renderPass;
+ pipelineInfo.subpass = 0;
+
+ pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;
+ pipelineInfo.basePipelineIndex = -1;
+
+ $[vktry {vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, NULL, &pipeline)}]
+ }
+
+ return (Pipeline) {
+ .pipeline = pipeline,
+ .pipelineLayout = pipelineLayout,
+
+ .id = id,
+ .pushConstantsSize = pushConstantsSize
+ };
+ }]
+
+ dc code {
+ static VkPipeline boundPipeline;
+ static VkDescriptorSet boundDescriptorSet;
+ }
+ dc proc drawStart {} void {
+ vkWaitForFences(device, 1, &inFlightFence, VK_TRUE, UINT64_MAX);
+
+ vkResetFences(device, 1, &inFlightFence);
+
+ vkAcquireNextImageKHR(device, swapchain, UINT64_MAX, imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex);
+
+ vkResetCommandBuffer(commandBuffer, 0);
+
+ VkCommandBufferBeginInfo beginInfo = {0};
+ beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+ beginInfo.flags = 0; // TODO: Should this be one-time?
+ beginInfo.pInheritanceInfo = NULL;
+ $[vktry {vkBeginCommandBuffer(commandBuffer, &beginInfo)}]
+
+ {
+ VkRenderPassBeginInfo renderPassInfo = {0};
+ renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
+ renderPassInfo.renderPass = renderPass;
+ renderPassInfo.framebuffer = swapchainFramebuffers[imageIndex];
+ renderPassInfo.renderArea.offset = (VkOffset2D) {0, 0};
+ renderPassInfo.renderArea.extent = swapchainExtent;
+
+ VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}};
+ renderPassInfo.clearValueCount = 1;
+ renderPassInfo.pClearValues = &clearColor;
+
+ vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
+ }
+
+ boundPipeline = VK_NULL_HANDLE;
+ boundDescriptorSet = VK_NULL_HANDLE;
+ }
+ dc proc drawImpl {Pipeline pipeline Tcl_Obj* pushConstantsObj} void {
+ if (boundPipeline != pipeline.pipeline) {
+ vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.pipeline);
+ boundPipeline = pipeline.pipeline;
+ }
+
+ if (boundDescriptorSet != imageDescriptorSet) {
+ vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS,
+ pipeline.pipelineLayout, 0, 1, &imageDescriptorSet, 0, NULL);
+ boundDescriptorSet = imageDescriptorSet;
+ }
+
+ {
+ int pushConstantsDataSize;
+ uint8_t* pushConstantsData = Tcl_GetByteArrayFromObj(pushConstantsObj, &pushConstantsDataSize);
+ if (pushConstantsDataSize != pipeline.pushConstantsSize) {
+ fprintf(stderr, "drawImpl: Expected push constants size %zu; push constants data size was %d\n",
+ pipeline.pushConstantsSize, pushConstantsDataSize);
+ exit(101);
+ }
+ vkCmdPushConstants(commandBuffer, pipeline.pipelineLayout,
+ VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0,
+ pipeline.pushConstantsSize, pushConstantsData);
+ }
+
+ // 1 quad -> 2 triangles -> 6 vertices
+ vkCmdDraw(commandBuffer, 6, 1, 0, 0);
+ }
+
+ # Draw to the screen using pipeline `pipeline`. Each arg in `args`
+ # should be a push-constant parameter of the pipeline. Can only be
+ # called between `drawStart` and `drawEnd`.
+ proc draw {pipeline args} {
+ variable WIDTH; variable HEIGHT
+ set args [linsert $args 0 [list $WIDTH $HEIGHT]]
+
+ drawImpl $pipeline [encodeArgs[Pipeline id $pipeline] {*}$args]
+ }
+
+ dc proc drawEnd {} void {
+ vkCmdEndRenderPass(commandBuffer);
+ $[vktry {vkEndCommandBuffer(commandBuffer)}]
+
+ VkSemaphore signalSemaphores[] = {renderFinishedSemaphore};
+ {
+ VkSubmitInfo submitInfo = {0};
+ submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+
+ VkSemaphore waitSemaphores[] = {imageAvailableSemaphore};
+ VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
+ submitInfo.waitSemaphoreCount = 1;
+ submitInfo.pWaitSemaphores = waitSemaphores;
+ submitInfo.pWaitDstStageMask = waitStages;
+
+ submitInfo.commandBufferCount = 1;
+ submitInfo.pCommandBuffers = &commandBuffer;
+
+ submitInfo.signalSemaphoreCount = 1;
+ submitInfo.pSignalSemaphores = signalSemaphores;
+
+ $[vktry {vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFence)}]
+ }
+ {
+ VkPresentInfoKHR presentInfo = {0};
+ presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
+ presentInfo.waitSemaphoreCount = 1;
+ presentInfo.pWaitSemaphores = signalSemaphores;
+
+ VkSwapchainKHR swapchains[] = {swapchain};
+ presentInfo.swapchainCount = 1;
+ presentInfo.pSwapchains = swapchains;
+ presentInfo.pImageIndices = &imageIndex;
+ presentInfo.pResults = NULL;
+
+ vkQueuePresentKHR(presentQueue, &presentInfo);
+ }
+ }
+
+ dc proc poll {} void {
+ $[expr { $macos ? { glfwPollEvents(); } : {} }]
+ }
+
+ # Construct a reusable GLSL function that can be linked into and
+ # called from a shader/pipeline.
+ proc fn {args rtype body} {
+ set fnArgs [list]
+ # We inline all dependent functions from the caller scope
+ # immediately here, since we don't know if those dependencies
+ # would be accessible/in scope at all when this function gets
+ # actually compiled into a shader.
+ set depFnDict [dict create]
+ foreach {argtype argname} $args {
+ if {$argtype eq "fn"} {
+ # TODO: Support fn being a list {fnName fn}.
+ dict set depFnDict [string map {: ""} $argname] [uplevel [list set $argname]]
+ } else {
+ lappend fnArgs $argtype $argname
+ }
+ }
+ return [list $fnArgs $depFnDict $rtype $body]
+ }
+
+ # Construct a shader pipeline that can be used to draw to the
+ # screen.
+ variable nextPipelineId 0
+ proc pipeline {args} {
+ if {[llength $args] == 3} {
+ lassign $args vertArgs vertBody fragBody
+ set fragArgs [list]
+ } elseif {[llength $args] == 4} {
+ lassign $args vertArgs vertBody fragArgs fragBody
+ } else {
+ error {Gpu::pipeline: should be used as [Gpu::pipeline vertArgs vertBody fragBody], or [Gpu::pipeline vertArgs vertBody fragArgs fragBody]}
+ }
+ set vertFnDict [dict create]
+ set fragFnDict [dict create]
+ set pushConstants [list]
+ set vertArgs [linsert $vertArgs 0 vec2 _resolution]
+ foreach {argtype argname} $vertArgs {
+ if {$argtype eq "fn"} {
+ # TODO: Support fn being a list {name fn}.
+ set fn [uplevel [list set $argname]]
+ set vertFnDict [dict merge $vertFnDict [lindex $fn 1]]
+ dict set vertFnDict [string map {: ""} $argname] $fn
+ continue
+ }
+ lappend pushConstants $argtype $argname
+ }
+ foreach {argtype argname} $fragArgs {
+ if {$argtype eq "fn"} {
+ # TODO: Support fn being a list {name fn}.
+ set fn [uplevel [list set $argname]]
+ set fragFnDict [dict merge $fragFnDict [lindex $fn 1]]
+ dict set fragFnDict [string map {: ""} $argname] $fn
+ continue
+ } else {
+ error "Fragment arguments not supported"
+ }
+ }
+
+ # Create a C subcompiler to create a fast routine to encode
+ # the push constants on each draw call.
+ set cc [c create]
+ $cc typedef int sampler2D
+ $cc struct vec2 { float x; float y; }
+ $cc struct vec3 { float x; float y; float z; }
+ $cc struct vec4 { float x; float y; float z; float w; }
+ $cc struct uvec4 { uint32_t x; uint32_t y; uint32_t z; uint32_t w; }
+
+ $cc argtype vec2 {
+ vec2 $argname;
+ {
+ int $[set argname]_objc; Tcl_Obj** $[set argname]_objv;
+ __ENSURE_OK(Tcl_ListObjGetElements(interp, $obj, &$[set argname]_objc, &$[set argname]_objv));
+ __ENSURE($[set argname]_objc == 2);
+ double x; __ENSURE_OK(Tcl_GetDoubleFromObj(interp, $[set argname]_objv[0], &x));
+ double y; __ENSURE_OK(Tcl_GetDoubleFromObj(interp, $[set argname]_objv[1], &y));
+ $argname = (vec2) { (float)x, (float)y };
+ }
+ }
+ $cc argtype vec3 {
+ vec3 $argname;
+ {
+ int $[set argname]_objc; Tcl_Obj** $[set argname]_objv;
+ __ENSURE_OK(Tcl_ListObjGetElements(interp, $obj, &$[set argname]_objc, &$[set argname]_objv));
+ __ENSURE($[set argname]_objc == 3);
+ double x; __ENSURE_OK(Tcl_GetDoubleFromObj(interp, $[set argname]_objv[0], &x));
+ double y; __ENSURE_OK(Tcl_GetDoubleFromObj(interp, $[set argname]_objv[1], &y));
+ double z; __ENSURE_OK(Tcl_GetDoubleFromObj(interp, $[set argname]_objv[2], &z));
+ $argname = (vec3) { (float)x, (float)y, (float)z };
+ }
+ }
+ $cc argtype vec4 {
+ vec4 $argname;
+ {
+ int $[set argname]_objc; Tcl_Obj** $[set argname]_objv;
+ __ENSURE_OK(Tcl_ListObjGetElements(interp, $obj, &$[set argname]_objc, &$[set argname]_objv));
+ __ENSURE($[set argname]_objc == 4);
+ double x; __ENSURE_OK(Tcl_GetDoubleFromObj(interp, $[set argname]_objv[0], &x));
+ double y; __ENSURE_OK(Tcl_GetDoubleFromObj(interp, $[set argname]_objv[1], &y));
+ double z; __ENSURE_OK(Tcl_GetDoubleFromObj(interp, $[set argname]_objv[2], &z));
+ double w; __ENSURE_OK(Tcl_GetDoubleFromObj(interp, $[set argname]_objv[3], &w));
+ $argname = (vec4) { (float)x, (float)y, (float)z, (float)w };
+ }
+ }
+ $cc argtype uvec4 {
+ uvec4 $argname;
+ {
+ int $[set argname]_objc; Tcl_Obj** $[set argname]_objv;
+ __ENSURE_OK(Tcl_ListObjGetElements(interp, $obj, &$[set argname]_objc, &$[set argname]_objv));
+ __ENSURE($[set argname]_objc == 4);
+ uint32_t x; __ENSURE_OK(Tcl_GetIntFromObj(interp, $[set argname]_objv[0], (int*) &x));
+ uint32_t y; __ENSURE_OK(Tcl_GetIntFromObj(interp, $[set argname]_objv[1], (int*) &y));
+ uint32_t z; __ENSURE_OK(Tcl_GetIntFromObj(interp, $[set argname]_objv[2], (int*) &z));
+ uint32_t w; __ENSURE_OK(Tcl_GetIntFromObj(interp, $[set argname]_objv[3], (int*) &w));
+ $argname = (uvec4) { (uint32_t)x, (uint32_t)y, (uint32_t)z, (uint32_t)w };
+ }
+ }
+ $cc code [csubst {
+ typedef struct Args {
+ $[join [lmap {argtype argname} $pushConstants {
+ expr {"_Alignas(sizeof($argtype)) $argtype $argname;"}
+ }] "\n"]
+ } Args;
+ }]
+ $cc include <stddef.h>
+ $cc proc getArgsSize {} int { return sizeof(Args); }
+ variable nextPipelineId
+ set pipelineId $nextPipelineId
+ $cc proc encodeArgs$pipelineId $pushConstants Tcl_Obj* {
+ Args args = {$[join [lmap {argtype argname} $pushConstants { subst {.$argname = $argname} }] " ,"]};
+ return Tcl_NewByteArrayObj((uint8_t *)&args, sizeof(args));
+ }
+ $cc compile
+ incr nextPipelineId
+
+ set pushConstantsSize [getArgsSize]
+
+ set pushConstantsCode [if {[llength $pushConstants] > 0} {
+ subst {
+ layout(push_constant) uniform Args {
+ [join [lmap {argtype argname} $pushConstants {
+ if {$argname eq "_"} continue
+ if {$argtype eq "sampler2D"} {
+ expr {"int $argname;"}
+ } else {
+ expr {"$argtype $argname;"}
+ }
+ }] "\n"]
+ } args;
+ }
+ }]
+
+ set vertShaderModule [createShaderModule [glslc -fshader-stage=vert [csubst {
+ #version 450
+
+ $pushConstantsCode
+
+ $[join [dict values [dict map {fnName fn} $vertFnDict {
+ lassign $fn fnArgs _ fnRtype fnBody
+ subst {
+ $fnRtype $fnName ([join [lmap {fnArgtype fnArgname} $fnArgs {subst {$fnArgtype $fnArgname}}] ", "]) {
+ $fnBody
+ }
+ }
+ }]] "\n"]
+
+ vec2 vert() {
+ $[join [lmap {argtype argname} $pushConstants {
+ if {$argname eq "_"} continue
+ if {$argtype eq "sampler2D"} continue
+ expr {"$argtype $argname = args.$argname;"}
+ }] " "]
+ $vertBody
+ }
+
+ void main() {
+ vec2 v = (2.0*vert() - args._resolution)/args._resolution;
+ gl_Position = vec4(v, 0.0, 1.0);
+ }
+ }]]]
+ # We pass the descriptor set with all images (samplers) to all
+ # fragment shaders, so we never need to rebind it (at draw
+ # time, the shader may get an index into the array if it's
+ # meant to draw an image).
+ #
+ # Note that we use combined image + sampler, instead of 1
+ # global sampler and multiple textures/images, because that's
+ # the only way to allow each image to have its own dimensions
+ # (dimensions are a property bound to the sampler).
+ set fragShaderModule [createShaderModule [glslc -fshader-stage=frag [csubst {
+ #version 450
+
+ layout(set = 0, binding = 0) uniform sampler2D _samplers[$ImageManager::GPU_MAX_IMAGES];
+
+ $pushConstantsCode
+
+ layout(location = 0) out vec4 outColor;
+
+ $[join [dict values [dict map {fnName fn} $fragFnDict {
+ lassign $fn fnArgs _ fnRtype fnBody
+ subst {
+ $fnRtype $fnName ([join [lmap {fnArgtype fnArgname} $fnArgs {subst {$fnArgtype $fnArgname}}] ", "]) {
+ $fnBody
+ }
+ }
+ }]] "\n"]
+
+ $[join [lmap {argtype argname} $pushConstants {
+ if {$argtype eq "sampler2D"} {
+ expr {"#define $argname _samplers\[args.$argname\]"}
+ }
+ }] "\n"]
+
+ vec4 frag() {
+ $[join [lmap {argtype argname} $pushConstants {
+ if {$argname eq "_"} continue
+ if {$argtype ne "sampler2D"} {
+ expr {"$argtype $argname = args.$argname;"}
+ }
+ }] " "]
+ $fragBody
+ }
+ void main() { outColor = frag(); }
+ }]]]
+
+ # pipeline needs to contain a specification of push constants,
+ # so they can be filled in at draw time.
+ set pipeline [Gpu::createPipeline $pipelineId $vertShaderModule $fragShaderModule \
+ $pushConstantsSize]
+ return $pipeline
+ }
+
+ namespace export dc
+ namespace eval ImageManager {
+ namespace import [namespace parent]::*
+
+ # TODO: Support more than 16 concurrent images. This
+ # constraint is imposed by Vulkan by default (device
+ # maxPerStageDescriptorSamplers limit is 16). I think we can
+ # increase it by querying GPU settings somehow.
+ variable GPU_MAX_IMAGES 16
+
+ # The technique used to manage images here is to have a
+ # single giant descriptor set for a giant GPU-side array of
+ # images, which all shaders can access. (That descriptor set
+ # _never_ has to be rebound; it stays bound through all draw
+ # calls, forever.)
+ #
+ # Each image has to be 'copied to the GPU' before you do any
+ # draw calls that use it. Copying an image to the GPU gives
+ # you a GPU-side image handle, which is just an integer index
+ # into the GPU-side array. You can pass that image handle
+ # into draw calls as a parameter (push constant) when you
+ # want to draw/use the image.
+ #
+ # See:
+ # - http://kylehalladay.com/blog/tutorial/vulkan/2018/01/28/Textue-Arrays-Vulkan.html
+ # - https://chunkstories.xyz/blog/a-note-on-descriptor-indexing/
+ # - https://gist.github.com/DethRaid/0171f3cfcce51950ee4ef96c64f59617
+ # - http://roar11.com/2019/06/vulkan-textures-unbound/
+ dc code {
+ VkDescriptorSetLayout imageDescriptorSetLayout;
+ VkDescriptorSet imageDescriptorSet;
+ }
+ dc proc imageManagerInit {} void {
+ // Set up imageDescriptorSetLayout:
+ {
+ /* VkDescriptorBindingFlags flags[1]; */
+ /* flags[0] = VK_DESCRIPTOR_BINDING_PARTIALLY_BOUND_BIT; */
+
+ /* VkDescriptorSetLayoutBindingFlagsCreateInfo bindingFlags = {0}; */
+ /* bindingFlags.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO; */
+ /* bindingFlags.bindingCount = 1; */
+ /* bindingFlags.pBindingFlags = flags; */
+
+ VkDescriptorSetLayoutBinding bindings[1];
+ memset(bindings, 0, sizeof(bindings));
+ bindings[0].binding = 0;
+ bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+ bindings[0].descriptorCount = $GPU_MAX_IMAGES;
+ bindings[0].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
+
+ VkDescriptorSetLayoutCreateInfo createInfo = {0};
+ createInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
+ createInfo.bindingCount = 1;
+ createInfo.pBindings = bindings;
+ /* createInfo.pNext = &bindingFlags; */
+
+ vkCreateDescriptorSetLayout(device, &createInfo, NULL, &imageDescriptorSetLayout);
+ }
+
+ VkDescriptorPool descriptorPool; {
+ VkDescriptorPoolSize poolSize = {0};
+ poolSize.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+ poolSize.descriptorCount = 512;
+
+ VkDescriptorPoolCreateInfo poolInfo = {0};
+ poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
+ poolInfo.poolSizeCount = 1;
+ poolInfo.pPoolSizes = &poolSize;
+ poolInfo.maxSets = 100;
+ $[vktry {vkCreateDescriptorPool(device, &poolInfo, NULL, &descriptorPool)}]
+ }
+
+ // Set up imageDescriptorSet:
+ {
+ VkDescriptorSetAllocateInfo allocInfo = {0};
+ allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
+ allocInfo.descriptorPool = descriptorPool;
+ allocInfo.descriptorSetCount = 1;
+ allocInfo.pSetLayouts = &imageDescriptorSetLayout;
+
+ $[vktry {vkAllocateDescriptorSets(device, &allocInfo, &imageDescriptorSet)}]
+ }
+ }
+
+ # Buffer allocation:
+ dc code [csubst {
+ uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) {
+ VkPhysicalDeviceMemoryProperties memProperties;
+ vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties);
+
+ for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
+ if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
+ return i;
+ }
+ }
+
+ exit(1);
+ }
+
+ void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties,
+ VkBuffer* buffer, VkDeviceMemory* bufferMemory) {
+ VkBufferCreateInfo bufferInfo = {0};
+ bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
+ bufferInfo.size = size;
+ bufferInfo.usage = usage;
+ bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+
+ $[vktry {vkCreateBuffer(device, &bufferInfo, NULL, buffer)}]
+
+ VkMemoryRequirements memRequirements;
+ vkGetBufferMemoryRequirements(device, *buffer, &memRequirements);
+
+ VkMemoryAllocateInfo allocInfo = {0};
+ allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+ allocInfo.allocationSize = memRequirements.size;
+ allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);
+
+ $[vktry {vkAllocateMemory(device, &allocInfo, NULL, bufferMemory)}]
+ vkBindBufferMemory(device, *buffer, *bufferMemory, 0);
+ }
+ }]
+
+ # Image allocation:
+ dc code [csubst {
+ void createImage(uint32_t width, uint32_t height,
+ VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties,
+ VkImage* image, VkDeviceMemory* imageMemory) {
+ VkImageCreateInfo imageInfo = {0};
+ imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
+ imageInfo.imageType = VK_IMAGE_TYPE_2D;
+ imageInfo.extent.width = width;
+ imageInfo.extent.height = height;
+ imageInfo.extent.depth = 1;
+ imageInfo.mipLevels = 1;
+ imageInfo.arrayLayers = 1;
+ imageInfo.format = format;
+ imageInfo.tiling = tiling;
+ imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+ imageInfo.usage = usage;
+ imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
+ imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+
+ $[vktry {vkCreateImage(device, &imageInfo, NULL, image)}]
+
+ VkMemoryRequirements memRequirements;
+ vkGetImageMemoryRequirements(device, *image, &memRequirements);
+
+ VkMemoryAllocateInfo allocInfo = {0};
+ allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+ allocInfo.allocationSize = memRequirements.size;
+ allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);
+
+ $[vktry {vkAllocateMemory(device, &allocInfo, NULL, imageMemory)}]
+
+ vkBindImageMemory(device, *image, *imageMemory, 0);
+ }
+ VkCommandBuffer beginSingleTimeCommands() {
+ VkCommandBufferAllocateInfo allocInfo = {0};
+ allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
+ allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
+ allocInfo.commandPool = commandPool;
+ allocInfo.commandBufferCount = 1;
+
+ VkCommandBuffer commandBuffer;
+ vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer);
+
+ VkCommandBufferBeginInfo beginInfo = {0};
+ beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+ beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
+
+ vkBeginCommandBuffer(commandBuffer, &beginInfo);
+
+ return commandBuffer;
+ }
+ void endSingleTimeCommands(VkCommandBuffer commandBuffer) {
+ vkEndCommandBuffer(commandBuffer);
+
+ VkSubmitInfo submitInfo = {0};
+ submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+ submitInfo.commandBufferCount = 1;
+ submitInfo.pCommandBuffers = &commandBuffer;
+
+ vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE);
+ vkQueueWaitIdle(graphicsQueue);
+
+ vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer);
+ }
+ void transitionImageLayout(VkImage image, VkFormat format,
+ VkImageLayout oldLayout, VkImageLayout newLayout) {
+ VkCommandBuffer commandBuffer = beginSingleTimeCommands();
+
+ VkImageMemoryBarrier barrier = {0};
+ barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+ barrier.oldLayout = oldLayout;
+ barrier.newLayout = newLayout;
+ barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+ barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+ barrier.image = image;
+ barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ barrier.subresourceRange.baseMipLevel = 0;
+ barrier.subresourceRange.levelCount = 1;
+ barrier.subresourceRange.baseArrayLayer = 0;
+ barrier.subresourceRange.layerCount = 1;
+
+ VkPipelineStageFlags sourceStage;
+ VkPipelineStageFlags destinationStage;
+ if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
+ barrier.srcAccessMask = 0;
+ barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+
+ sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
+ destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
+ } else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
+ barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
+ barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
+
+ sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
+ destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
+ } else {
+ exit(91);
+ }
+ vkCmdPipelineBarrier(commandBuffer,
+ sourceStage, destinationStage,
+ 0,
+ 0, NULL,
+ 0, NULL,
+ 1, &barrier);
+
+ endSingleTimeCommands(commandBuffer);
+ }
+ }]
+
+ # Allocates(!) a copy of `im` with 4 channels, on the Tcl
+ # local heap. Up to the caller to free it.
+ dc proc copyImageToRgba {image_t im} image_t {
+ if (im.components == 4) return im;
+ if (im.components != 1 && im.components != 3) exit(2);
+
+ image_t ret = im;
+ ret.components = 4;
+ ret.bytesPerRow = ret.width * ret.components;
+ ret.data = ckalloc(ret.bytesPerRow * ret.height);
+
+ if (im.components == 3) {
+ for (int y = 0; y < im.height; y++) {
+ for (int x = 0; x < im.width; x++) {
+ int imidx = y*im.bytesPerRow + x*im.components;
+ int r = im.data[imidx+0],
+ g = im.data[imidx+1],
+ b = im.data[imidx+2];
+
+ int ridx = y*ret.bytesPerRow + x*ret.components;
+ ret.data[ridx+0] = r;
+ ret.data[ridx+1] = g;
+ ret.data[ridx+2] = b;
+ ret.data[ridx+3] = 255;
+ }
+ }
+ } else {
+ for (int y = 0; y < im.height; y++) {
+ for (int x = 0; x < im.width; x++) {
+ int imidx = y*im.bytesPerRow + x*im.components;
+ int r = im.data[imidx],
+ g = im.data[imidx],
+ b = im.data[imidx];
+
+ int ridx = y*ret.bytesPerRow + x*ret.components;
+ ret.data[ridx+0] = r;
+ ret.data[ridx+1] = g;
+ ret.data[ridx+2] = b;
+ ret.data[ridx+3] = 255;
+ }
+ }
+ }
+ return ret;
+ }
+
+ dc typedef int gpu_image_handle_t
+ dc code [csubst {
+ // Points to all GPU-side data structures associated with
+ // an image (that we will destroy when we evict that
+ // image).
+ typedef struct gpu_image_block {
+ bool alive;
+
+ VkImage textureImage;
+ VkDeviceMemory textureImageMemory;
+ VkImageView textureImageView;
+ VkSampler textureSampler;
+ } gpu_image_block;
+ gpu_image_block gpuImages[$GPU_MAX_IMAGES];
+
+ gpu_image_handle_t allocateGpuImageHandle() {
+ for (int i = 0; i < $GPU_MAX_IMAGES; i++) {
+ if (!gpuImages[i].alive) { return i; }
+ }
+ fprintf(stderr, "Gpu: Exceeded GPU_MAX_IMAGES\n"); exit(1);
+ }
+ }]
+ dc proc copyImageToGpu {image_t im0} gpu_image_handle_t {
+ gpu_image_handle_t imageId = allocateGpuImageHandle();
+ gpu_image_block* block = &gpuImages[imageId];
+ block->alive = true;
+
+ // Image must be RGBA.
+ image_t im;
+ if (im0.components != 4) { im = copyImageToRgba(im0); }
+ else { im = im0; }
+
+ size_t size = im.width * im.height * 4;
+
+ VkBuffer stagingBuffer;
+ VkDeviceMemory stagingBufferMemory;
+ createBuffer(size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
+ VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
+ &stagingBuffer, &stagingBufferMemory);
+
+ createImage(im.width, im.height,
+ VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_TILING_OPTIMAL,
+ VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+ &block->textureImage, &block->textureImageMemory);
+
+ // Set up block->textureImageView:
+ {
+ VkImageViewCreateInfo viewInfo = {0};
+ viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+ viewInfo.image = block->textureImage;
+ viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
+ viewInfo.format = VK_FORMAT_R8G8B8A8_SRGB;
+ viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ viewInfo.subresourceRange.baseMipLevel = 0;
+ viewInfo.subresourceRange.levelCount = 1;
+ viewInfo.subresourceRange.baseArrayLayer = 0;
+ viewInfo.subresourceRange.layerCount = 1;
+ $[vktry {vkCreateImageView(device, &viewInfo, NULL, &block->textureImageView)}]
+ }
+ // Set up block->textureSampler:
+ {
+ VkSamplerCreateInfo samplerInfo = {0};
+ samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
+ samplerInfo.magFilter = VK_FILTER_LINEAR;
+ samplerInfo.minFilter = VK_FILTER_LINEAR;
+ samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+ samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+ samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
+ samplerInfo.anisotropyEnable = VK_FALSE; // TODO: do we want this?
+ samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
+ samplerInfo.unnormalizedCoordinates = VK_FALSE;
+ samplerInfo.compareEnable = VK_FALSE;
+ samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS;
+ samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
+ samplerInfo.mipLodBias = 0.0f;
+ samplerInfo.minLod = 0.0f;
+ samplerInfo.maxLod = 0.0f;
+ $[vktry {vkCreateSampler(device, &samplerInfo, NULL, &block->textureSampler)}]
+ }
+
+ // Copy im to stagingBuffer:
+ {
+ void* data; vkMapMemory(device, stagingBufferMemory, 0, size, 0, &data);
+ for (int y = 0; y < im.height; y++) {
+ memcpy(data + y*im.width*4,
+ im.data + y*im.bytesPerRow,
+ im.width*4);
+ }
+ vkUnmapMemory(device, stagingBufferMemory);
+ }
+ // Copy stagingBuffer to block->textureImage:
+ {
+ transitionImageLayout(block->textureImage, VK_FORMAT_R8G8B8A8_SRGB,
+ VK_IMAGE_LAYOUT_UNDEFINED,
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
+
+ VkCommandBuffer commandBuffer = beginSingleTimeCommands();
+
+ VkBufferImageCopy region = {0};
+ region.bufferOffset = 0;
+ region.bufferRowLength = 0;
+ region.bufferImageHeight = 0;
+
+ region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ region.imageSubresource.mipLevel = 0;
+ region.imageSubresource.baseArrayLayer = 0;
+ region.imageSubresource.layerCount = 1;
+
+ region.imageOffset = (VkOffset3D) {0, 0, 0};
+ region.imageExtent = (VkExtent3D) {im.width, im.height, 1};
+ vkCmdCopyBufferToImage(commandBuffer,
+ stagingBuffer,
+ block->textureImage,
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ 1,
+ &region);
+
+ endSingleTimeCommands(commandBuffer);
+
+ transitionImageLayout(block->textureImage, VK_FORMAT_R8G8B8A8_SRGB,
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
+ }
+
+ vkDestroyBuffer(device, stagingBuffer, NULL);
+ vkFreeMemory(device, stagingBufferMemory, NULL);
+
+ // Write this image+sampler to the imageDescriptorSet
+ static bool didInitializeDescriptors = false;
+ {
+ VkDescriptorImageInfo imageInfo = {0};
+ imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+ imageInfo.imageView = block->textureImageView;
+ imageInfo.sampler = block->textureSampler;
+
+ if (!didInitializeDescriptors) {
+ // Hack: if we're not using the descriptor
+ // indexing extension, we can't have a partially
+ // bound descriptor set, so we need to fill all
+ // the slots in the image array with
+ // _something_. We just fill all slots wtih the
+ // first image for now. See
+ // http://roar11.com/2019/06/vulkan-textures-unbound/
+ VkWriteDescriptorSet descriptorWrites[$GPU_MAX_IMAGES] = {0};
+ for (int i = 0; i < $GPU_MAX_IMAGES; i++) {
+ descriptorWrites[i].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ descriptorWrites[i].dstSet = imageDescriptorSet;
+ descriptorWrites[i].dstBinding = 0;
+ descriptorWrites[i].dstArrayElement = i;
+ descriptorWrites[i].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+ descriptorWrites[i].descriptorCount = 1;
+ descriptorWrites[i].pImageInfo = &imageInfo;
+ }
+ vkUpdateDescriptorSets(device, $GPU_MAX_IMAGES, descriptorWrites, 0, NULL);
+ didInitializeDescriptors = true;
+ } else {
+ VkWriteDescriptorSet descriptorWrite = {0};
+ descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ descriptorWrite.dstSet = imageDescriptorSet;
+ descriptorWrite.dstBinding = 0;
+ descriptorWrite.dstArrayElement = imageId;
+ descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+ descriptorWrite.descriptorCount = 1;
+ descriptorWrite.pImageInfo = &imageInfo;
+ vkUpdateDescriptorSets(device, 1, &descriptorWrite, 0, NULL);
+ }
+ }
+
+ if (im0.components != 4) { ckfree(im.data); }
+ return imageId;
+ }
+ dc proc freeGpuImage {gpu_image_handle_t gim} void {
+ gpu_image_block* block = &gpuImages[gim];
+ block->alive = false;
+
+ vkDeviceWaitIdle(device); // TODO: this is probably slow
+
+ // HACK: Use GPU image 0 as a placeholder so it doesn't
+ // fault if it tries to access this freed image.
+ {
+ VkDescriptorImageInfo imageInfo = {0};
+ imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+ imageInfo.imageView = gpuImages[0].textureImageView;
+ imageInfo.sampler = gpuImages[0].textureSampler;
+
+ VkWriteDescriptorSet descriptorWrite = {0};
+ descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ descriptorWrite.dstSet = imageDescriptorSet;
+ descriptorWrite.dstBinding = 0;
+ descriptorWrite.dstArrayElement = gim;
+ descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
+ descriptorWrite.descriptorCount = 1;
+ descriptorWrite.pImageInfo = &imageInfo;
+ vkUpdateDescriptorSets(device, 1, &descriptorWrite, 0, NULL);
+ }
+
+ vkDestroyImage(device, block->textureImage, NULL);
+ vkFreeMemory(device, block->textureImageMemory, NULL);
+ vkDestroySampler(device, block->textureSampler, NULL);
+ vkDestroyImageView(device, block->textureImageView, NULL);
+ }
+ }
+
+ if {!$macos} {
+ # Needed on Raspberry Pi 4:
+ c loadlibLd libatomic.so
+ }
+
+ dc compile
+
+ proc glslc {args} {
+ set cmdargs [lreplace $args end end]
+ set glsl [lindex $args end]
+ set glslfd [file tempfile glslfile glslfile.glsl]; puts $glslfd $glsl; close $glslfd
+ split [string map {\n ""} [exec glslc {*}$cmdargs -mfmt=num -o - $glslfile]] ","
+ }
+}
+
+source "lib/colors.tcl"
proc Start-display-process {code} {
if {$::tcl_platform(os) eq "Darwin"} {
@@ -6,7 +1550,9 @@ proc Start-display-process {code} {
# don't Start a subprocess here. We trampoline this code into
# a claim so we can run the rest of display.folk before
# getting caught in its infinite loop.
- Claim "fake-display-process" has program code $code
+ upvar makeGpu makeGpu
+ Claim "fake-display-process" has program code \
+ [list apply [list {makeGpu} $code] $makeGpu]
When the GPU is ready & /someone/ wishes program code /co/ runs on "display" {
eval $co
@@ -20,7 +1566,9 @@ Start-display-process {
puts "Display pid: [pid]"
set macos [expr {$::tcl_platform(os) eq "Darwin"}]
- source pi/Gpu.tcl
+ source "virtual-programs/images.folk"
+ namespace eval Gpu $makeGpu
+
if {[info hostname] eq "gadget-blue" || [info hostname] eq "gadget-green"} {
# HACK: we use HDMI1 instead of HDMI0 on these
Gpu::init 1
@@ -81,7 +1629,7 @@ Start-display-process {
return res;
}]
- source "pi/Colors.tcl"
+ source "lib/colors.tcl"
Wish $::thisProcess receives statements like \
[list /someone/ wishes the GPU /...anything/]
diff --git a/virtual-programs/images.folk b/virtual-programs/images.folk
index 002760c0..2fceeb6e 100644
--- a/virtual-programs/images.folk
+++ b/virtual-programs/images.folk
@@ -33,7 +33,6 @@ namespace eval ::image {
}
$cc cflags -ljpeg -lpng
- source "pi/cUtils.tcl"
defineImageType $cc
$cc include <stdlib.h>
$cc include <string.h>
diff --git a/virtual-programs/keyboard.folk b/virtual-programs/keyboard.folk
index fe1730e5..f28da39b 100644
--- a/virtual-programs/keyboard.folk
+++ b/virtual-programs/keyboard.folk
@@ -47,7 +47,7 @@ set keyboardDevices [walkInputEventPaths]
foreach keyboard $keyboardDevices {
Claim the keyboards are $keyboardDevices
Start process "keyboard-$keyboard" {
- source "pi/KeyCodes.tcl"
+ source "lib/key-codes.tcl"
set KEY_STATES [list up down repeat]
Wish $::thisProcess shares statements like \
diff --git a/virtual-programs/print.folk b/virtual-programs/print.folk
index 012e2879..06a7820d 100644
--- a/virtual-programs/print.folk
+++ b/virtual-programs/print.folk
@@ -1,5 +1,3 @@
-source "pi/cUtils.tcl"
-
set cc [c create]
$cc cflags -I$::env(HOME)/apriltag -Wall -Werror $::env(HOME)/apriltag/libapriltag.a
c loadlib $::env(HOME)/apriltag/libapriltag.so