builtin-programs/gpu/draw.folk
# draw.folk --
#
# Provides the ability to run pixel shaders with image and
# numerical parameters (so you can draw images, shapes, etc.)
# Single render thread handles all displays.
if {[info exists this] && $::tcl_platform(os) eq "darwin"} {
# We hard-code draw.folk into thread 0, so we should abort if not
# running that way.
return
}
When the GPU library is /gpuLib/ &\
the GPU Vulkan handle type definer is /defineVulkanHandleType/ &\
the GPU texture library is /gpuTextureLib/ &\
the GPU pipeline library is /pipelineLib/ &\
the GPU pipeline compiler library is /pipelineCompilerLib/ {
fn defineVulkanHandleType
# On macOS we always use GLFW; on Linux we use direct Vulkan display.
set useGlfw [expr {$::tcl_platform(os) eq "darwin"}]
set cc [C]
$cc cflags -I./vendor
$cc include <stdlib.h>
$cc include <unistd.h>
$cc include <pthread.h>
$cc code {
#define VOLK_IMPLEMENTATION
#include "volk/volk.h"
}
if {$useGlfw} {
$cc code {
// This must be included _after_ volk.h (Vulkan).
#include <GLFW/glfw3.h>
}
$cc endcflags -lglfw
}
$cc extend $gpuLib
$cc extend $pipelineLib
local proc vktry {call} { string map {\n " "} [csubst {{
VkResult res = $call;
if (res != VK_SUCCESS) {
/* TODO: We also need to unwind all the GPU state. */
FOLK_ERROR("Failed $call: %s (%d)\n", VkResultToString(res), res);
}
}}] }
$cc code [subst {
typedef struct DisplayState {
VkSurfaceKHR surface;
VkSwapchainKHR swapchain;
uint32_t swapchainImageCount;
VkFormat swapchainImageFormat;
VkFramebuffer* swapchainFramebuffers;
VkExtent2D swapchainExtent;
uint32_t imageIndex;
VkSemaphore imageAvailableSemaphore;
VkSemaphore* renderFinishedSemaphores;
VkFence inFlightFence;
[expr { $useGlfw ? "GLFWwindow* window;" : "" }]
} DisplayState;
VkDevice device;
VkPhysicalDevice physicalDevice;
static DisplayState* currentDisplay;
static VkPipeline boundPipeline;
static VkDescriptorSet boundDescriptorSet;
}]
$cc argtype DisplayState* {
DisplayState* $argname;
sscanf(Jim_String($obj), "(DisplayState*) %p", &$argname);
}
$cc rtype DisplayState* {
char buf[100];
snprintf(buf, 100, "(DisplayState*) %p", $rvalue);
$robj = Jim_NewStringObj(interp, buf, -1);
}
$cc proc initDisplay {char* display uint32_t width uint32_t height uint32_t refreshRate} DisplayState* {
volkInitialize();
volkLoadInstanceOnly(*instance_ptr());
device = *device_ptr();
volkLoadDevice(device);
physicalDevice = *physicalDevice_ptr();
DisplayState* ds = calloc(1, sizeof(DisplayState));
// Get drawing surface.
$[expr { $useGlfw ? {
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
ds->window = glfwCreateWindow(width, height, "Folk", NULL, NULL);
FOLK_ENSURE(ds->window != NULL);
if (glfwCreateWindowSurface(*instance_ptr(), ds->window, NULL, &ds->surface) != VK_SUCCESS) {
FOLK_ERROR("Failed to create GLFW window surface");
}
} : [csubst {
// Search through displays to find the one matching display name
int retries = 0;
VkDisplayKHR chosenDisplay = VK_NULL_HANDLE;
while (true) {
uint32_t displayCount;
vkGetPhysicalDeviceDisplayPropertiesKHR(physicalDevice, &displayCount, NULL);
VkDisplayPropertiesKHR displayProps[displayCount];
vkGetPhysicalDeviceDisplayPropertiesKHR(physicalDevice, &displayCount, displayProps);
for (uint32_t i = 0; i < displayCount; i++) {
if (displayProps[i].displayName && strcmp(displayProps[i].displayName, display) == 0) {
chosenDisplay = displayProps[i].display;
break;
}
}
if (chosenDisplay != VK_NULL_HANDLE) {
break;
}
retries++;
if (retries > 10) {
free(ds);
FOLK_ERROR("Failed to find display '%s'\n", display);
} else {
fprintf(stderr, "gpu/draw: Failed to find display '%s'; retrying (retry %d).\n",
display, retries);
usleep(1000000);
}
}
// Search through modes to find one matching width, height, and refreshRate
uint32_t modeCount;
vkGetDisplayModePropertiesKHR(physicalDevice, chosenDisplay, &modeCount, NULL);
VkDisplayModePropertiesKHR modeProps[modeCount];
vkGetDisplayModePropertiesKHR(physicalDevice, chosenDisplay, &modeCount, modeProps);
int chosenMode = -1;
for (uint32_t i = 0; i < modeCount; i++) {
if (modeProps[i].parameters.visibleRegion.width == width &&
modeProps[i].parameters.visibleRegion.height == height &&
// For some reason, refresh rate can vary by 1Hz-ish on the AnyBeam.
abs((int)modeProps[i].parameters.refreshRate - (int)refreshRate) < 1000) {
chosenMode = i;
break;
}
}
if (chosenMode == -1) {
free(ds);
FOLK_ERROR("Failed to find display mode on display '%s' "
"with width=%u height=%u refreshRate=%u\n",
display, width, height, refreshRate);
}
// Find a display plane that supports chosenDisplay.
uint32_t planeCount;
vkGetPhysicalDeviceDisplayPlanePropertiesKHR(physicalDevice, &planeCount, NULL);
VkDisplayPlanePropertiesKHR planeProps[planeCount];
vkGetPhysicalDeviceDisplayPlanePropertiesKHR(physicalDevice, &planeCount, planeProps);
uint32_t chosenPlane = UINT32_MAX;
for (uint32_t p = 0; p < planeCount; p++) {
// Skip planes already bound to a different display.
if (planeProps[p].currentDisplay != VK_NULL_HANDLE &&
planeProps[p].currentDisplay != chosenDisplay) {
continue;
}
// Check that this plane supports our display.
uint32_t supportedCount;
vkGetDisplayPlaneSupportedDisplaysKHR(physicalDevice, p, &supportedCount, NULL);
VkDisplayKHR supported[supportedCount];
vkGetDisplayPlaneSupportedDisplaysKHR(physicalDevice, p, &supportedCount, supported);
for (uint32_t s = 0; s < supportedCount; s++) {
if (supported[s] == chosenDisplay) {
chosenPlane = p;
break;
}
}
if (chosenPlane != UINT32_MAX) break;
}
fprintf(stderr, "chosenPlane for '%s': %u\n", display, chosenPlane);
if (chosenPlane == UINT32_MAX) {
free(ds);
FOLK_ERROR("Failed to find a display plane for display '%s'\n", display);
}
VkDisplaySurfaceCreateInfoKHR createInfo = {0};
createInfo.sType = VK_STRUCTURE_TYPE_DISPLAY_SURFACE_CREATE_INFO_KHR;
createInfo.displayMode = modeProps[chosenMode].displayMode;
createInfo.planeIndex = chosenPlane;
createInfo.transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
createInfo.alphaMode = VK_DISPLAY_PLANE_ALPHA_PER_PIXEL_BIT_KHR;
createInfo.imageExtent = modeProps[chosenMode].parameters.visibleRegion;
$[vktry {vkCreateDisplayPlaneSurfaceKHR(*instance_ptr(), &createInfo, NULL, &ds->surface)}]
}] }]
uint32_t presentQueueFamilyIndex; {
VkBool32 presentSupport = 0;
vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice, *graphicsQueueFamilyIndex_ptr(), ds->surface, &presentSupport);
if (!presentSupport) {
fprintf(stderr, "Vulkan graphics queue family doesn't support presenting to surface\n"); exit(1);
}
presentQueueFamilyIndex = *graphicsQueueFamilyIndex_ptr();
}
// Figure out capabilities/format/mode of physical device for surface.
VkSurfaceCapabilitiesKHR capabilities;
VkExtent2D extent;
uint32_t imageCount;
VkSurfaceFormatKHR surfaceFormat;
VkPresentModeKHR presentMode; {
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, ds->surface, &capabilities);
if (capabilities.currentExtent.width != UINT32_MAX) {
extent = capabilities.currentExtent;
} else {
$[expr { $useGlfw ? {
glfwGetFramebufferSize(ds->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, ds->surface, &formatCount, NULL);
VkSurfaceFormatKHR formats[formatCount];
if (formatCount == 0) { fprintf(stderr, "No supported surface formats.\n"); exit(1); }
vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, ds->surface, &formatCount, formats);
surfaceFormat = formats[0]; // semi-arbitrary default
for (int i = 0; i < formatCount; i++) {
if (formats[i].format == VK_FORMAT_B8G8R8A8_UNORM) {
surfaceFormat = formats[i];
}
}
uint32_t presentModeCount;
vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, ds->surface, &presentModeCount, NULL);
VkPresentModeKHR presentModes[presentModeCount];
if (presentModeCount == 0) { fprintf(stderr, "No supported present modes.\n"); exit(1); }
vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, ds->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
{
VkSwapchainCreateInfoKHR createInfo = {0};
createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
createInfo.surface = ds->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_ptr() != 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, &ds->swapchain)}]
}
vkGetSwapchainImagesKHR(device, ds->swapchain, &ds->swapchainImageCount, NULL);
VkImage swapchainImages[ds->swapchainImageCount];
{
vkGetSwapchainImagesKHR(device, ds->swapchain, &ds->swapchainImageCount, swapchainImages);
ds->swapchainImageFormat = surfaceFormat.format;
ds->swapchainExtent = extent;
}
VkImageView swapchainImageViews[ds->swapchainImageCount]; {
for (size_t i = 0; i < ds->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 = ds->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])}]
}
}
if (ds->swapchainImageFormat != VK_FORMAT_B8G8R8A8_UNORM) {
FOLK_ERROR("Swapchain image format %d does not match pipeline render pass format (VK_FORMAT_B8G8R8A8_UNORM)\n", ds->swapchainImageFormat);
}
// Set up framebuffers
ds->swapchainFramebuffers = (VkFramebuffer *) malloc(sizeof(VkFramebuffer) * ds->swapchainImageCount);
for (size_t i = 0; i < ds->swapchainImageCount; i++) {
VkImageView attachments[] = { swapchainImageViews[i] };
VkFramebufferCreateInfo framebufferInfo = {0};
framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
framebufferInfo.renderPass = *renderPass_ptr();
framebufferInfo.attachmentCount = 1;
framebufferInfo.pAttachments = attachments;
framebufferInfo.width = ds->swapchainExtent.width;
framebufferInfo.height = ds->swapchainExtent.height;
framebufferInfo.layers = 1;
$[vktry {vkCreateFramebuffer(device, &framebufferInfo, NULL, &ds->swapchainFramebuffers[i])}]
}
{
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, &ds->imageAvailableSemaphore)}]
ds->renderFinishedSemaphores = calloc(ds->swapchainImageCount, sizeof(VkSemaphore));
for (uint32_t i = 0; i < ds->swapchainImageCount; i++) {
$[vktry {vkCreateSemaphore(device, &semaphoreInfo, NULL, &ds->renderFinishedSemaphores[i])}]
}
$[vktry {vkCreateFence(device, &fenceInfo, NULL, &ds->inFlightFence)}]
}
return ds;
}
$cc proc getDisplayWidth {DisplayState* ds} uint32_t { return ds->swapchainExtent.width; }
$cc proc getDisplayHeight {DisplayState* ds} uint32_t { return ds->swapchainExtent.height; }
$cc proc drawStart {DisplayState* ds} void {
currentDisplay = ds;
VkCommandBuffer commandBuffer = getCommandBuffer();
vkResetFences(device, 1, &ds->inFlightFence);
VkResult acquireResult = vkAcquireNextImageKHR(device, ds->swapchain, UINT64_MAX,
ds->imageAvailableSemaphore, VK_NULL_HANDLE,
&ds->imageIndex);
if (acquireResult != VK_SUCCESS && acquireResult != VK_SUBOPTIMAL_KHR) {
FOLK_ERROR("Failed vkAcquireNextImageKHR: %s (%d)\n",
VkResultToString(acquireResult), acquireResult);
}
vkResetCommandBuffer(commandBuffer, 0);
VkCommandBufferBeginInfo beginInfo = {0};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
beginInfo.pInheritanceInfo = NULL;
$[vktry {vkBeginCommandBuffer(commandBuffer, &beginInfo)}]
{
VkRenderPassBeginInfo renderPassInfo = {0};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
renderPassInfo.renderPass = *renderPass_ptr();
renderPassInfo.framebuffer = ds->swapchainFramebuffers[ds->imageIndex];
renderPassInfo.renderArea.offset = (VkOffset2D) {0, 0};
renderPassInfo.renderArea.extent = ds->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;
}
# 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`.
$cc proc draw {Pipeline pipeline Jim_Obj* argsObj} void {
VkCommandBuffer commandBuffer = getCommandBuffer();
if (boundPipeline != pipeline.pipeline) {
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.pipeline);
boundPipeline = pipeline.pipeline;
VkViewport viewport = {0}; {
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.width = (float) currentDisplay->swapchainExtent.width;
viewport.height = (float) currentDisplay->swapchainExtent.height;
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
}
vkCmdSetViewport(commandBuffer, 0, 1, &viewport);
VkRect2D scissor = {0}; {
scissor.offset = (VkOffset2D) {0, 0};
scissor.extent = currentDisplay->swapchainExtent;
}
vkCmdSetScissor(commandBuffer, 0, 1, &scissor);
}
VkDescriptorSet currentDescriptorSet = getTextureDescriptorSet();
if (boundDescriptorSet != currentDescriptorSet) {
bindTextureDescriptorSet(commandBuffer, pipeline.pipelineLayout);
boundDescriptorSet = currentDescriptorSet;
}
{
uint8_t pushConstantsData[128];
int pushConstantsDataSize = pipeline.encodePushConstants->encode(interp, argsObj, pushConstantsData);
if (pushConstantsDataSize == -1) {
FOLK_ABORT();
}
if (pushConstantsDataSize != pipeline.pushConstantsSize) {
FOLK_ERROR("Gpu draw: Expected push constants size %zu; push constants data size was %d\n",
pipeline.pushConstantsSize, pushConstantsDataSize);
}
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);
}
$cc proc drawEnd {} void {
DisplayState* ds = currentDisplay;
VkCommandBuffer commandBuffer = getCommandBuffer();
vkCmdEndRenderPass(commandBuffer);
$[vktry {vkEndCommandBuffer(commandBuffer)}]
VkSemaphore signalSemaphores[] = {ds->renderFinishedSemaphores[ds->imageIndex]};
{
VkSubmitInfo submitInfo = {0};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
VkSemaphore waitSemaphores[] = {ds->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;
pthread_mutex_lock(graphicsQueueMutex_ptr());
$[vktry {vkQueueSubmit(*graphicsQueue_ptr(), 1, &submitInfo, ds->inFlightFence)}]
pthread_mutex_unlock(graphicsQueueMutex_ptr());
}
{
VkPresentInfoKHR presentInfo = {0};
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
presentInfo.waitSemaphoreCount = 1;
presentInfo.pWaitSemaphores = signalSemaphores;
VkSwapchainKHR swapchains[] = {ds->swapchain};
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = swapchains;
presentInfo.pImageIndices = &ds->imageIndex;
presentInfo.pResults = NULL;
pthread_mutex_lock(graphicsQueueMutex_ptr());
VkResult presentResult = vkQueuePresentKHR(*presentQueue_ptr(), &presentInfo);
pthread_mutex_unlock(graphicsQueueMutex_ptr());
if (presentResult != VK_SUCCESS && presentResult != VK_SUBOPTIMAL_KHR) {
FOLK_ERROR("Failed vkQueuePresentKHR: %s (%d)\n",
VkResultToString(presentResult), presentResult);
}
}
// Wait for this display's submission to complete before we
// reuse the shared command buffer for the next display.
vkWaitForFences(device, 1, &ds->inFlightFence, VK_TRUE, UINT64_MAX);
currentDisplay = NULL;
}
$cc proc poll {} void {
$[expr { $useGlfw ? { glfwPollEvents(); } : {} }]
}
proc makeGpu {gpuLib pipelineLib drawLib} { return [library create gpu {gpuLib pipelineLib drawLib} {
variable gpuLib
variable pipelineLib
variable drawLib
if {![exists -command $drawLib]} {
# Load manually so we don't need to call a command.
$drawLib
}
foreach drawLibCmd [info commands "$drawLib *"] {
lassign $drawLibCmd _ drawLibCmd
proc $drawLibCmd {args} {drawLib drawLibCmd} {
tailcall "$drawLib $drawLibCmd" {*}$args
}
}
}] }
set drawLib [$cc compile]
Claim the GPU draw library is $drawLib
set gpu [makeGpu $gpuLib $pipelineLib $drawLib]
tracy setThreadName "gpu"
# Track initialized displays: dict mapping display name -> DisplayState*
set displays [dict create]
set kGpu [tracy makeString "gpu"]
set kLatency [tracy makeString "latency"]
set kQuadCount [tracy makeString "quadCount"]
set kRegionCount [tracy makeString "regionCount"]
set kDrawCount [tracy makeString "drawCount"]
fn QueryAllFns! {} {
set fns [dict create]
ForEach! /someone/ claims the GPU compiles function /name/ to /fn/ {
dict set fns $name $fn
}
return $fns
}
fn tryCompileFn {wisher name source} {
try {
set fns [QueryAllFns!]
set fn [$pipelineCompilerLib fn $fns {*}$source]
# Technically a misnomer: the function is just stored, not
# compiled until it's been inlined into a shader.
Claim the GPU compiles function $name to $fn
} on 99 notFoundName {
puts "gpu fn $name: Waiting for $notFoundName"
When the GPU compiles function $notFoundName to /anything/ {
puts "Did compile $notFoundName"
tryCompileFn $wisher $name $source
}
} on error e {
puts stderr "Error: GPU compiles function $name: [errorInfo $e]"
Claim $wisher has error $e with info [errorInfo $e]
}
}
fn tryCompilePipeline {wisher name source} {
try {
set fns [QueryAllFns!]
set pipeline [$pipelineCompilerLib pipeline $fns {*}$source]
puts "gpu: tryCompilePipeline: Compiled $name"
Claim the GPU compiles pipeline $name to $pipeline
} on 99 notFoundName {
puts "gpu pipeline $name: Waiting for $notFoundName"
When the GPU compiles function $notFoundName to /anything/ {
puts "Did compile $notFoundName"
tryCompilePipeline $wisher $name $source
}
} on error e {
puts stderr "Error: GPU compiles pipeline $name: [errorInfo $e]"
Claim $wisher has error $e with info [errorInfo $e]
}
}
When /wisher/ wishes the GPU compiles function /name/ /source/ {
tryCompileFn $wisher $name $source
}
When /wisher/ wishes the GPU compiles pipeline /name/ /source/ {
tryCompilePipeline $wisher $name $source
}
set missingPipelines [dict create]
while true {
# Advance texture retirement once per frame, then run canvas redraw work.
$gpuTextureLib beginTextureFrame
ForEach! /someone/ wishes the GPU runs frame prelude handler /hd/ {
{*}$hd
}
# Query draw commands once (shared across all displays).
set results [Query! /someone/ claims the GPU compiles pipeline /name/ to /pipeline/]
set pipelines [dict create]
foreach result $results { dict with result { dict set pipelines $name $pipeline } }
set displayList [dict create]
foreach result [Query! /wisher/ wishes the GPU draws pipeline /name/ with /...options/] {
try {
set name [dict get $result name]
if {![dict exists $pipelines $name]} {
if {![dict exists $missingPipelines $name]} {
puts stderr "gpu: Missing pipeline $name"
dict set missingPipelines $name true
}
continue
}
dict unset missingPipelines $name
set pipeline [dict get $pipelines [dict get $result name]]
set options [dict get $result options]
set layer [dict getdef $options layer 0]
if {[dict exists $options instances]} {
set instances [dict get $options instances]
} else {
set instances [list [dict get $options arguments]]
}
foreach instance $instances {
dict lappend displayList $layer \
[list $gpu draw $pipeline $instance]
}
} on error e {
puts stderr "Error: GPU draws pipeline $name: [errorInfo $e]"
Assert! $this claims [dict get $result wisher] has error $e with info [errorInfo $e]
# TODO: does this ever get disposed?
}
}
# Query active displays; init new ones on first sight.
foreach result [Query! /someone/ wishes $::thisNode uses display /display/ with /...displayOpts/] {
set display [dict get $result display]
set displayOpts [dict get $result displayOpts]
if {![dict exists $displays $display]} {
if {[llength [Query! $::thisNode has display $display with /...any/]] == 0} {
puts stderr "gpu/draw: Display '$display' is wished but not enumerated; skipping"
dict set displays $display missing
continue
}
set ds [$drawLib initDisplay $display \
$displayOpts(width) $displayOpts(height) \
$displayOpts(refreshRate)]
dict set displays $display $ds
Assert! display $display has width [$drawLib getDisplayWidth $ds] \
height [$drawLib getDisplayHeight $ds]
} elseif {[dict get $displays $display] eq "missing"} {
# TODO: Allow for retry if display is plugged in later?
continue
}
}
# Make textures published while building the display list drawable
# before we record display command buffers.
$gpuTextureLib drainDeferredTextureOps
# Draw to each active display.
dict for {displayName ds} $displays {
if {$ds eq "missing"} { continue }
$gpu drawStart $ds
set drawCount 0
foreach layer [lsort -real [dict keys $displayList]] {
set layerDisplayList [dict get $displayList $layer]
foreach displayCommand $layerDisplayList {
incr drawCount
try { {*}$displayCommand } \
on error e { puts stderr [errorInfo $e] }
}
}
$gpu drawEnd
}
tracy frameMarkNamed $kGpu
if {$useGlfw} { $drawLib poll }
# TODO: sleep for 5ms or something?
}
}