Created
February 6, 2025 04:54
-
-
Save iggyvolz/9a3b051591a1728972cccd5b73e2af79 to your computer and use it in GitHub Desktop.
Dawn triangle in PHP
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
use Amp\DeferredFuture; | |
use Amp\Future; | |
use FFI\CData; | |
use Revolt\EventLoop; | |
require_once __DIR__ . '/vendor/autoload.php'; | |
$dawn = FFI::cdef(file_get_contents(__DIR__ . '/webgpu.h'), '/home/iggyvolz/dawn/build/src/dawn/native/libwebgpu_dawn.so'); | |
$glfw = FFI::cdef(file_get_contents(__DIR__ . '/glfw3.h'), '/home/iggyvolz/dawn/build/third_party/glfw/src/libglfw.so.3.5'); | |
if($glfw->glfwInit() === 0) throw new Exception("GLFW init failed"); | |
$glfw->glfwWindowHint(/* GLFW_CLIENT_API */ 0x00022001, /* GLFW_NO_API */0); | |
$window = $glfw->glfwCreateWindow(512, 512, "Dawn", NULL, NULL); | |
$glfw->glfwShowWindow($window); | |
$instanceDescriptor = $dawn->new("WGPUInstanceDescriptor"); | |
$instance = $dawn->wgpuCreateInstance(FFI::addr($instanceDescriptor)); | |
$requestAdapterOptions = $dawn->new("WGPURequestAdapterOptions"); | |
[$status, $adapter, $message] = wgpuFuture(fn(Closure $cb): null => $dawn->wgpuInstanceRequestAdapter($instance, FFI::addr($requestAdapterOptions), $cb, null))->await(); | |
if($status !== 1) throw new Exception(stringView($message)); | |
$deviceDescriptor = $dawn->new("WGPUDeviceDescriptor"); | |
//typedef void (*WGPUUncapturedErrorCallback)(WGPUDevice const * device, WGPUErrorType type, struct WGPUStringView message, void* userdata1, void* userdata2) ; | |
$deviceDescriptor->uncapturedErrorCallbackInfo2->callback = function(CData $device, int $type, CData $message): void { | |
echo ("Uncaptured error: " . stringView($message) . "\n"); | |
throw new RuntimeException("Uncaptured error: " . stringView($message) . "\n"); | |
}; | |
// WGPURequestDeviceStatus status, WGPUDevice device, struct WGPUStringView message, void * userdata | |
[$status, $device, $message] = wgpuFuture(fn(Closure $cb): null => $dawn->wgpuAdapterRequestDevice($adapter, FFI::addr($deviceDescriptor), $cb, null))->await(); | |
if($status !== 1) throw new Exception(stringView($message)); | |
$sourceSurface = $dawn->new("WGPUSurfaceSourceWaylandSurface"); | |
$sourceSurface->chain->sType = 0x00000007; | |
$sourceSurface->display = $glfw->glfwGetWaylandDisplay(); | |
$sourceSurface->surface = $glfw->glfwGetWaylandWindow($window); | |
$descriptor = $dawn->new("WGPUSurfaceDescriptor"); | |
$descriptor->nextInChain = $dawn->cast("struct WGPUChainedStruct*", FFI::addr($sourceSurface)); | |
$surface = $dawn->wgpuInstanceCreateSurface($instance, FFI::addr($descriptor)); | |
$surfaceCapabilities = $dawn->new("WGPUSurfaceCapabilities"); | |
$status = $dawn->wgpuSurfaceGetCapabilities($surface, $adapter, FFI::addr($surfaceCapabilities)); | |
if($status !== 1) throw new Exception("Failed to get surface capabilities"); | |
for($i = 0; $i < $surfaceCapabilities->formatCount; $i++) { | |
var_dump($surfaceCapabilities->formats[$i]); | |
} | |
$surfaceConfiguration = $dawn->new("WGPUSurfaceConfiguration"); | |
$surfaceConfiguration->device = $device; | |
$surfaceConfiguration->format = $format = $surfaceCapabilities->formats[1]; | |
$surfaceConfiguration->usage = $surfaceCapabilities->usages & ~0x0000000000000008; | |
$surfaceConfiguration->width = 512; | |
$surfaceConfiguration->height = 512; | |
$surfaceConfiguration->presentMode = $surfaceCapabilities->presentModes[0]; | |
$dawn->wgpuSurfaceConfigure($surface, FFI::addr($surfaceConfiguration)); | |
$shaderCode = <<<EOF | |
@vertex fn vertexMain(@builtin(vertex_index) i : u32) -> | |
@builtin(position) vec4f { | |
const pos = array(vec2f(0, 1), vec2f(-1, -1), vec2f(1, -1)); | |
return vec4f(pos[i], 0, 1); | |
} | |
@fragment fn fragmentMain() -> @location(0) vec4f { | |
return vec4f(1, 0, 0, 1); | |
} | |
EOF; | |
$shaderModule = withStringView($dawn, $shaderCode, function(CData $shaderString) use($device, $dawn): CData { | |
$wgslDesc = $dawn->new("WGPUShaderSourceWGSL"); | |
$wgslDesc->code = $shaderString; | |
$wgslDesc->chain->sType = 0x00000002; | |
$shaderModuleDescriptor = $dawn->new("WGPUShaderModuleDescriptor"); | |
$shaderModuleDescriptor->nextInChain = $dawn->cast("struct WGPUChainedStruct*", FFI::addr($wgslDesc)); | |
return $dawn->wgpuDeviceCreateShaderModule($device, FFI::addr($shaderModuleDescriptor)); | |
}); | |
$colorTargetState = $dawn->new("WGPUColorTargetState"); | |
$colorTargetState->format = $format; | |
$colorTargetState->writeMask = /* WGPUColorWriteMask_All*/ 0x000000000000000F; | |
$pipeline = withStringView($dawn, "fragmentMain", function(CData $fragmentEntryPoint) use($device, $dawn, $shaderModule, $colorTargetState): CData { | |
$fragmentState = $dawn->new("WGPUFragmentState"); | |
$fragmentState->module = $shaderModule; | |
$fragmentState->targetCount = 1; | |
$fragmentState->entryPoint = $fragmentEntryPoint; | |
$fragmentState->targets = FFI::addr($colorTargetState); | |
return withStringView($dawn, "vertexMain", function(CData $vertexEntryPoint) use($device, $dawn, $shaderModule, $fragmentState): CData { | |
$renderPipelineDescriptor = $dawn->new("WGPURenderPipelineDescriptor"); | |
$renderPipelineDescriptor->vertex->module = $shaderModule; | |
$renderPipelineDescriptor->vertex->entryPoint = $vertexEntryPoint; | |
$renderPipelineDescriptor->multisample->count = 1; | |
$renderPipelineDescriptor->multisample->mask = 0xFFFFFFFF; | |
$renderPipelineDescriptor->fragment = FFI::addr($fragmentState); | |
return $dawn->wgpuDeviceCreateRenderPipeline($device, FFI::addr($renderPipelineDescriptor)); | |
}); | |
}); | |
$surfaceTexture = $dawn->new("WGPUSurfaceTexture"); | |
$attachment = $dawn->new("WGPURenderPassColorAttachment"); | |
$attachment->loadOp = /* WGPULoadOp_Clear */0x00000002; | |
$attachment->storeOp = /* WGPUStoreOp_Store */0x00000001; | |
$attachment->depthSlice = -1; | |
$attachment->clearValue->r = 0; | |
$textureViewDescriptor = $dawn->new("WGPUTextureViewDescriptor"); | |
$textureViewDescriptor->arrayLayerCount = -1; | |
$textureViewDescriptor->mipLevelCount = -1; | |
$renderPassDescriptor = $dawn->new("WGPURenderPassDescriptor"); | |
$renderPassDescriptor->colorAttachmentCount = 1; | |
$renderPassDescriptor->colorAttachments = FFI::addr($attachment); | |
$encoderDescriptor = $dawn->new("WGPUCommandEncoderDescriptor"); | |
$bufferDescriptor = $dawn->new("WGPUCommandBufferDescriptor"); | |
$last = microtime(true); | |
while(!$glfw->glfwWindowShouldClose($window)) { | |
$now = microtime(true); | |
echo 1/($now - $last) . " fps, " . 1000*($now - $last) . "ms\n"; | |
$last = $now; | |
$glfw->glfwPollEvents(); | |
$dawn->wgpuSurfaceGetCurrentTexture($surface, FFI::addr($surfaceTexture)); | |
$attachment->view = $dawn->wgpuTextureCreateView($surfaceTexture->texture, FFI::addr($textureViewDescriptor)); | |
$encoder = $dawn->wgpuDeviceCreateCommandEncoder($device, FFI::addr($encoderDescriptor)); | |
$pass = $dawn->wgpuCommandEncoderBeginRenderPass($encoder, FFI::addr($renderPassDescriptor)); | |
$dawn->wgpuRenderPassEncoderSetPipeline($pass, $pipeline); | |
$dawn->wgpuRenderPassEncoderDraw($pass, 3, 1, 0, 0); | |
$dawn->wgpuRenderPassEncoderEnd($pass); | |
$commands = $dawn->wgpuCommandEncoderFinish($encoder, FFI::addr($bufferDescriptor)); | |
$queue = $dawn->wgpuDeviceGetQueue($device); | |
$dawn->wgpuQueueSubmit($queue, 1, FFI::addr($commands)); | |
$dawn->wgpuSurfacePresent($surface); | |
$dawn->wgpuInstanceProcessEvents($instance); | |
} | |
function wgpuFuture(callable $fn): Future | |
{ | |
$future = new DeferredFuture(); | |
$fn(fn(mixed ...$args) => $future->complete($args)); | |
return $future->getFuture(); | |
} | |
function stringView(CData $cData): string | |
{ | |
return FFI::string($cData->data, $cData->length); | |
} | |
/** | |
* @template T | |
* @param Closure(CData):T $callback Callback taking a string view as the first parameter, which is immediately executed | |
* @return T | |
*/ | |
function withStringView(FFI $dawn, string $str, Closure $callback): mixed | |
{ | |
$buf = $dawn->new("char[" . strlen($str) . "]", owned: false); | |
try { | |
FFI::memcpy($buf, $str, strlen($str)); | |
$stringView = $dawn->new("struct WGPUStringView"); | |
$stringView->data = $buf; | |
$stringView->length = strlen($str); | |
return $callback($stringView); | |
} finally { | |
FFI::free($buf); | |
} | |
} | |
EventLoop::run(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment