-
-
Save rsms/a891d9904378576e8fb3e11a3fb957fa to your computer and use it in GitHub Desktop.
UPDATE: Now in dedicated repo: https://github.com/rsms/dawn-wire-example
This file contains 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
This has been tested on macOS with clang 12 | |
Setup dawn: | |
git clone https://dawn.googlesource.com/dawn dawn | |
cd dawn | |
git checkout --detach 40d1c83362bebfe0fede9ca3e2bf802d6b217455 -- | |
cp scripts/standalone.gclient .gclient | |
gclient sync | |
cd .. | |
Setup libev: (used for I/O) | |
mkdir -p libev | |
cd libev | |
wget http://dist.schmorp.de/libev/libev-4.33.tar.gz | |
INSTALL_PREFIX=$PWD | |
cd libev-4.33 | |
./configure --prefix="$INSTALL_PREFIX" --disable-shared | |
make -j$(nproc) | |
make install | |
cd ../.. | |
Configure: | |
mkdir -p out/debug | |
cd out/debug | |
cmake -G Ninja -DCMAKE_BUILD_TYPE=$CMAKE_BUILD_TYPE ../.. | |
cd ../.. | |
Build: | |
ninja -C out/debug a-server a-client | |
Run: | |
Terminal 1: | |
out/debug/a-server | |
Terminal 2: | |
out/debug/a-client |
This file contains 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
// Copyright 2017 The Dawn Authors | |
// | |
// Licensed under the Apache License, Version 2.0 (the "License"); | |
// you may not use this file except in compliance with the License. | |
// You may obtain a copy of the License at | |
// | |
// http://www.apache.org/licenses/LICENSE-2.0 | |
// | |
// Unless required by applicable law or agreed to in writing, software | |
// distributed under the License is distributed on an "AS IS" BASIS, | |
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
// See the License for the specific language governing permissions and | |
// limitations under the License. | |
#include "dawn/examples/SampleUtils.h" | |
#include "utils/BackendBinding.h" | |
#include "utils/GLFWUtils.h" | |
#include "utils/TerribleCommandBuffer.h" | |
#include "utils/SystemUtils.h" | |
#include "utils/WGPUHelpers.h" | |
#include <dawn/webgpu.h> | |
#include <dawn/dawn_proc.h> | |
#include <dawn/dawn_wsi.h> | |
#include <dawn_wire/WireClient.h> | |
#include <dawn_wire/WireServer.h> | |
#include <iostream> | |
#include <thread> | |
#include <unistd.h> // pipe | |
#include <sys/socket.h> | |
#include <sys/un.h> | |
#include <fcntl.h> // F_GETFL, O_NONBLOCK etc | |
// silence "mangled name of 'ev_set_allocator' will change in C++17" | |
_Pragma("GCC diagnostic push") | |
_Pragma("GCC diagnostic ignored \"-Wc++17-compat-mangling\"") | |
#include <ev.h> | |
_Pragma("GCC diagnostic pop") | |
typedef struct ev_loop RunLoop; | |
#define DLOG_PREFIX "\e[1;36m[client]\e[0m " | |
#ifdef DEBUG | |
#define dlog(format, ...) ({ \ | |
fprintf(stderr, DLOG_PREFIX format " \e[2m(%s %d)\e[0m\n", \ | |
##__VA_ARGS__, __FUNCTION__, __LINE__); \ | |
fflush(stderr); \ | |
}) | |
#define errlog(format, ...) \ | |
(({ fprintf(stderr, "E " format " (%s:%d)\n", ##__VA_ARGS__, __FILE__, __LINE__); \ | |
fflush(stderr); })) | |
#else | |
#define dlog(...) do{}while(0) | |
#define errlog(format, ...) \ | |
(({ fprintf(stderr, "E " format "\n", ##__VA_ARGS__); fflush(stderr); })) | |
#endif | |
static bool FDSetNonBlock(int fd) { | |
#ifdef _WIN32 | |
unsigned long arg = 1; | |
ioctlsocket(_get_osfhandle(fd), FIONBIO, &arg); // from libev/ev.c | |
#else | |
int flags = fcntl(fd, F_GETFL); | |
if (flags < 0 || | |
fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0 || | |
fcntl(fd, F_SETFD, FD_CLOEXEC)) // FD_CLOEXEC for fork | |
{ | |
errno = EWOULDBLOCK; | |
return false; | |
} | |
#endif | |
return true; | |
} | |
int createUNIXSocket(const char* filename, sockaddr_un* addr) { | |
addr->sun_family = AF_UNIX; | |
auto filenameLen = strlen(filename); | |
if (filenameLen > sizeof(addr->sun_path)-1) { | |
errno = ENAMETOOLONG; | |
return -1; | |
} | |
memcpy(addr->sun_path, filename, filenameLen+1); | |
return socket(AF_UNIX, SOCK_STREAM, 0); | |
} | |
int createUNIXSocketServer(const char* filename) { | |
/*struct*/ sockaddr_un addr; | |
int fd = createUNIXSocket(filename, &addr); | |
if (fd > -1) { | |
unlink(filename); | |
int acceptQueueSize = 5; | |
if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1 || | |
listen(fd, acceptQueueSize) == -1) | |
{ | |
int e = errno; | |
close(fd); | |
unlink(filename); | |
errno = e; | |
fd = -1; | |
} | |
} | |
return fd; | |
} | |
int connectUNIXSocket(const char* filename) { | |
/*struct*/ sockaddr_un addr; | |
int fd = createUNIXSocket(filename, &addr); | |
if (fd > -1) { | |
if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { | |
int e = errno; | |
close(fd); | |
errno = e; | |
fd = -1; | |
} | |
} | |
return fd; | |
} | |
#define COMMAND_BUFFER_SIZE 4096*4 | |
class LolCommandBuffer : public dawn_wire::CommandSerializer { | |
dawn_wire::CommandHandler* mHandler = nullptr; | |
size_t mOffset = 0; | |
char mBuffer[COMMAND_BUFFER_SIZE]; | |
const char* mName = ""; | |
public: | |
int r = -1; // file descriptor to read from | |
int w = -1; // file descriptor to write to | |
LolCommandBuffer(const char* name) : mName(name) {} | |
LolCommandBuffer(dawn_wire::CommandHandler* handler) : mHandler(handler) {} | |
void SetHandler(dawn_wire::CommandHandler* handler) { mHandler = handler; } | |
size_t GetMaximumAllocationSize() const override { | |
return sizeof(mBuffer); | |
} | |
void* GetCmdSpace(size_t size) override { | |
assert(size <= sizeof(mBuffer)); | |
char* result = &mBuffer[mOffset]; | |
if (sizeof(mBuffer) - size < mOffset) { | |
if (!Flush()) | |
return nullptr; | |
return GetCmdSpace(size); | |
} | |
mOffset += size; | |
return result; | |
} | |
bool Flush() override { | |
if (mOffset == 0) | |
return true; | |
bool success = true; | |
// success = mHandler->HandleCommands(mBuffer, mOffset) != nullptr; | |
if (w != -1) { | |
printf("cmdbuf %s Flush write %zu bytes\n", mName, mOffset); | |
ssize_t z = ::write(w, mBuffer, mOffset); | |
if (size_t(z) != mOffset) { | |
perror("cmdbuf Flush write"); | |
success = false; | |
} | |
} | |
mOffset = 0; | |
return success; | |
} | |
}; | |
static std::unique_ptr<dawn_native::Instance> instance; | |
static dawn_wire::WireClient* wireClient = nullptr; | |
static LolCommandBuffer* c2sBuf = nullptr; | |
static WGPUDevice device; | |
static WGPUQueue queue; | |
static WGPUSwapChain swapchain; | |
static WGPURenderPipeline pipeline; | |
static WGPUTextureFormat swapChainFormat; | |
static void PrintDeviceError(WGPUErrorType errorType, const char* message, void*) { | |
const char* errorTypeName = ""; | |
switch (errorType) { | |
case WGPUErrorType_Validation: | |
errorTypeName = "Validation"; | |
break; | |
case WGPUErrorType_OutOfMemory: | |
errorTypeName = "Out of memory"; | |
break; | |
case WGPUErrorType_Unknown: | |
errorTypeName = "Unknown"; | |
break; | |
case WGPUErrorType_DeviceLost: | |
errorTypeName = "Device lost"; | |
break; | |
default: | |
UNREACHABLE(); | |
return; | |
} | |
std::cerr << "device error: " << errorTypeName << " error: " << message << std::endl; | |
} | |
wgpu::Device createWebGPUDevice() { | |
instance = std::make_unique<dawn_native::Instance>(); | |
// utils::DiscoverAdapter(instance.get(), window, backendType); | |
instance->DiscoverDefaultAdapters(); | |
DawnProcTable procs; | |
c2sBuf = new LolCommandBuffer("c2s"); | |
dawn_wire::WireClientDescriptor clientDesc = {}; | |
clientDesc.serializer = c2sBuf; | |
wireClient = new dawn_wire::WireClient(clientDesc); | |
procs = dawn_wire::client::GetProcs(); | |
auto deviceReservation = wireClient->ReserveDevice(); | |
//wireServer->InjectDevice(backendDevice, deviceReservation.id, deviceReservation.generation); | |
WGPUDevice cDevice = deviceReservation.device; | |
dawnProcSetProcs(&procs); | |
procs.deviceSetUncapturedErrorCallback(cDevice, PrintDeviceError, nullptr); | |
return wgpu::Device::Acquire(cDevice); | |
} | |
void flushWireBuffers() { | |
bool c2sSuccess = c2sBuf->Flush(); | |
ASSERT(c2sSuccess); | |
} | |
void configureSwapchain(int width, int height) { | |
WGPUSwapChainDescriptor descriptor = {}; | |
// descriptor.implementation = binding->GetSwapChainImplementation(); | |
//descriptor.implementation = dawn_native::null::CreateNativeSwapChainImpl(); | |
descriptor.format = WGPUTextureFormat_RGBA8Unorm; | |
descriptor.presentMode = WGPUPresentMode_Immediate; | |
swapchain = wgpuDeviceCreateSwapChain(device, nullptr, &descriptor); | |
// wgpu::TextureFormat textureFormat = static_cast<wgpu::TextureFormat>( | |
// binding->GetPreferredSwapChainTextureFormat()) | |
// swapChainFormat = static_cast<WGPUTextureFormat>(textureFormat); | |
// swapChainFormat = WGPUTextureFormat_RGBA8Unorm; | |
wgpuSwapChainConfigure(swapchain, descriptor.format, WGPUTextureUsage_RenderAttachment, | |
width, height); | |
} | |
void init_dawn() { | |
// device = CreateCppDawnDevice().Release(); | |
// device = createWebGPUDevice().Release(); | |
queue = wgpuDeviceGetQueue(device); | |
configureSwapchain(640, 480); | |
const char* vs = | |
"[[builtin(vertex_index)]] var<in> VertexIndex : u32;\n" | |
"[[builtin(position)]] var<out> Position : vec4<f32>;\n" | |
"const pos : array<vec2<f32>, 3> = array<vec2<f32>, 3>(\n" | |
" vec2<f32>( 0.0, 0.5),\n" | |
" vec2<f32>(-0.5, -0.5),\n" | |
" vec2<f32>( 0.5, -0.5)\n" | |
");\n" | |
"[[stage(vertex)]] fn main() -> void {\n" | |
" Position = vec4<f32>(pos[VertexIndex], 0.0, 1.0);\n" | |
" return;\n" | |
"}\n"; | |
WGPUShaderModule vsModule = utils::CreateShaderModule(device, vs).Release(); | |
const char* fs = | |
"[[location(0)]] var<out> fragColor : vec4<f32>;\n" | |
"[[stage(fragment)]] fn main() -> void {\n" | |
" fragColor = vec4<f32>(1.0, 0.0, 0.7, 1.0);\n" | |
" return;\n" | |
"}\n"; | |
WGPUShaderModule fsModule = utils::CreateShaderModule(device, fs).Release(); | |
{ | |
WGPURenderPipelineDescriptor2 descriptor = {}; | |
// Fragment state | |
WGPUBlendState blend = {}; | |
blend.color.operation = WGPUBlendOperation_Add; | |
blend.color.srcFactor = WGPUBlendFactor_One; | |
blend.color.dstFactor = WGPUBlendFactor_One; | |
blend.alpha.operation = WGPUBlendOperation_Add; | |
blend.alpha.srcFactor = WGPUBlendFactor_One; | |
blend.alpha.dstFactor = WGPUBlendFactor_One; | |
WGPUColorTargetState colorTarget = {}; | |
colorTarget.format = swapChainFormat; | |
colorTarget.blend = &blend; | |
colorTarget.writeMask = WGPUColorWriteMask_All; | |
WGPUFragmentState fragment = {}; | |
fragment.module = fsModule; | |
fragment.entryPoint = "main"; | |
fragment.targetCount = 1; | |
fragment.targets = &colorTarget; | |
descriptor.fragment = &fragment; | |
// Other state | |
descriptor.layout = nullptr; | |
descriptor.depthStencil = nullptr; | |
descriptor.vertex.module = vsModule; | |
descriptor.vertex.entryPoint = "main"; | |
descriptor.vertex.bufferCount = 0; | |
descriptor.vertex.buffers = nullptr; | |
descriptor.multisample.count = 1; | |
descriptor.multisample.mask = 0xFFFFFFFF; | |
descriptor.multisample.alphaToCoverageEnabled = false; | |
descriptor.primitive.frontFace = WGPUFrontFace_CCW; | |
descriptor.primitive.cullMode = WGPUCullMode_None; | |
descriptor.primitive.topology = WGPUPrimitiveTopology_TriangleList; | |
descriptor.primitive.stripIndexFormat = WGPUIndexFormat_Undefined; | |
pipeline = wgpuDeviceCreateRenderPipeline2(device, &descriptor); | |
} | |
wgpuShaderModuleRelease(vsModule); | |
wgpuShaderModuleRelease(fsModule); | |
} | |
uint32_t fc = 0; | |
bool animate = true; | |
void render_frame() { | |
fc++; | |
float RED = 0.4; | |
float GREEN = 0.4; | |
float BLUE = 0.4; | |
if (animate) { | |
RED = abs(sinf(float(fc) / 100)); | |
GREEN = abs(sinf(float(fc) / 90)); | |
BLUE = abs(cosf(float(fc) / 80)); | |
} | |
WGPUTextureView backbufferView = wgpuSwapChainGetCurrentTextureView(swapchain); | |
WGPURenderPassDescriptor renderpassInfo = {}; | |
WGPURenderPassColorAttachmentDescriptor colorAttachment = {}; | |
{ | |
colorAttachment.attachment = backbufferView; | |
colorAttachment.resolveTarget = nullptr; | |
colorAttachment.clearColor = {RED, GREEN, BLUE, 0.0f}; | |
colorAttachment.loadOp = WGPULoadOp_Clear; | |
colorAttachment.storeOp = WGPUStoreOp_Store; | |
renderpassInfo.colorAttachmentCount = 1; | |
renderpassInfo.colorAttachments = &colorAttachment; | |
renderpassInfo.depthStencilAttachment = nullptr; | |
} | |
WGPUCommandBuffer commands; | |
{ | |
WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(device, nullptr); | |
WGPURenderPassEncoder pass = wgpuCommandEncoderBeginRenderPass(encoder, &renderpassInfo); | |
wgpuRenderPassEncoderSetPipeline(pass, pipeline); | |
wgpuRenderPassEncoderDraw(pass, 3, 1, 0, 0); | |
wgpuRenderPassEncoderEndPass(pass); | |
wgpuRenderPassEncoderRelease(pass); | |
commands = wgpuCommandEncoderFinish(encoder, nullptr); | |
wgpuCommandEncoderRelease(encoder); | |
} | |
wgpuQueueSubmit(queue, 1, &commands); | |
wgpuCommandBufferRelease(commands); | |
wgpuSwapChainPresent(swapchain); | |
wgpuTextureViewRelease(backbufferView); | |
if (!c2sBuf->Flush()) // blocks on write I/O | |
dlog("c2sBuf->Flush() failed"); | |
//flushWireBuffers(); | |
} | |
const char* sockfile = "server.sock"; | |
char rbuf[COMMAND_BUFFER_SIZE]; | |
struct { | |
RunLoop* rl; | |
ev_io w; | |
bool gotWelcomeMessage; | |
} conn; | |
static void close_connection() { | |
assert(conn.rl != nullptr); | |
ev_io_stop(conn.rl, &conn.w); | |
::close(conn.w.fd); | |
} | |
// client_fd_cb is called when a client's connection has available I/O | |
static void client_fd_cb(RunLoop* rl, ev_io* w, int revents) { | |
dlog("I/O %s %s", | |
revents & EV_READ ? "EV_READ" : "", | |
revents & EV_WRITE ? "EV_WRITE" : ""); | |
int fd = w->fd; | |
const char* welcomeMessage = "OHAI\n"; | |
if (revents & EV_READ) { | |
ssize_t n = read(fd, rbuf, sizeof(rbuf)); | |
dlog("read %zd bytes", n); | |
if (n == 0) { | |
close_connection(); | |
return; | |
} | |
if (!conn.gotWelcomeMessage) { | |
if ((size_t)n >= strlen(welcomeMessage) || | |
memcmp(rbuf, welcomeMessage, strlen(welcomeMessage)) == 0) | |
{ | |
dlog("received welcome message from server"); | |
conn.gotWelcomeMessage = true; | |
n -= strlen(welcomeMessage); | |
} else { | |
dlog("expected welcome message but got something else; closing connection"); | |
close_connection(); | |
return; | |
} | |
} | |
if (n > 0) { | |
// feed data to wire client | |
bool success = wireClient->HandleCommands(rbuf, (size_t)n) != nullptr; | |
dlog("wireClient->HandleCommands => %s", success ? "ok" : "fail"); | |
} | |
} | |
} | |
static void client_poll_timeout_cb(RunLoop* rl, ev_timer* w, int revents) { | |
dlog("render"); | |
double t = ev_time(); | |
render_frame(); | |
t = ev_time() - t; | |
dlog("frame time: %.2f ms", t * 1000.0); | |
ev_timer_again(rl, w); | |
} | |
void runloop_main(int fd) { | |
RunLoop* rl = EV_DEFAULT; | |
device = createWebGPUDevice().Release(); | |
c2sBuf->w = fd; | |
init_dawn(); | |
::memset(&conn, 0, sizeof(conn)); | |
conn.rl = rl; | |
FDSetNonBlock(fd); | |
ev_io_init(&conn.w, client_fd_cb, fd, EV_READ); | |
ev_io_start(rl, &conn.w); | |
ev_timer timeout_w; | |
ev_init(&timeout_w, client_poll_timeout_cb); | |
timeout_w.repeat = 1.0; //1.0 / 60.0; | |
ev_timer_again(rl, &timeout_w); | |
ev_unref(rl); // don't allow timer to keep runloop alive alone | |
// returns when the connection closes (when there are no more watchers) | |
while (1) { | |
if (ev_run(rl, EVRUN_ONCE) == 0) { | |
// no more active watchers | |
dlog("io: no active watchers -- exit runloop"); | |
break; | |
} | |
} | |
} | |
int main(int argc, const char* argv[]) { | |
while (1) { | |
dlog("connecting to UNIX socket \"%s\"", sockfile); | |
int fd = connectUNIXSocket(sockfile); | |
if (fd < 0) { | |
perror("connectUNIXSocket"); | |
sleep(1); | |
continue; | |
} | |
dlog("connected to socket"); | |
runloop_main(fd); | |
close(fd); | |
} | |
dlog("exit"); | |
return 0; | |
} |
This file contains 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
cmake_minimum_required(VERSION 3.10) | |
set_property(GLOBAL PROPERTY USE_FOLDERS ON) | |
project(dawn-test) | |
if(NOT CMAKE_BUILD_TYPE) | |
set(CMAKE_BUILD_TYPE Debug) | |
endif() | |
set(CMAKE_CXX_STANDARD 14) | |
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fcolor-diagnostics") | |
set(CMAKE_LINK_FLAGS "${CMAKE_LINK_FLAGS} -fcolor-diagnostics") | |
add_subdirectory("dawn" EXCLUDE_FROM_ALL) | |
add_executable(a-server | |
"a-server.cc" | |
) | |
target_link_libraries(a-server | |
dawn_internal_config | |
dawncpp | |
dawn_proc | |
dawn_common | |
dawn_native | |
dawn_wire | |
dawn_utils | |
glfw | |
"ev" | |
) | |
add_executable(a-client | |
"a-client.cc" | |
) | |
target_link_libraries(a-client | |
dawn_internal_config | |
dawncpp | |
dawn_proc | |
dawn_common | |
dawn_native | |
dawn_wire | |
dawn_utils | |
glfw | |
"ev" | |
) | |
target_link_directories(a-server PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/libev/lib ) | |
target_link_directories(a-client PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/libev/lib ) | |
target_include_directories(a-server PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/libev/include ) | |
target_include_directories(a-client PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/libev/include ) | |
if (${CMAKE_BUILD_TYPE} MATCHES "Debug") | |
target_compile_definitions(a-server PRIVATE DEBUG=1) | |
target_compile_definitions(a-client PRIVATE DEBUG=1) | |
target_compile_options(a-server PRIVATE | |
-fcolor-diagnostics | |
-Wall -g -O0 | |
"-ffile-prefix-map=../../=" | |
) | |
target_compile_options(a-client PRIVATE | |
-fcolor-diagnostics | |
-Wall -g -O0 | |
"-ffile-prefix-map=../../=" | |
) | |
endif() |
This file contains 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
// Copyright 2017 The Dawn Authors | |
// | |
// Licensed under the Apache License, Version 2.0 (the "License"); | |
// you may not use this file except in compliance with the License. | |
// You may obtain a copy of the License at | |
// | |
// http://www.apache.org/licenses/LICENSE-2.0 | |
// | |
// Unless required by applicable law or agreed to in writing, software | |
// distributed under the License is distributed on an "AS IS" BASIS, | |
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
// See the License for the specific language governing permissions and | |
// limitations under the License. | |
#include "dawn/examples/SampleUtils.h" | |
#include "utils/BackendBinding.h" | |
#include "utils/GLFWUtils.h" | |
#include "utils/TerribleCommandBuffer.h" | |
#include "utils/SystemUtils.h" | |
#include "utils/WGPUHelpers.h" | |
#include "GLFW/glfw3.h" | |
#include <dawn/webgpu.h> | |
#include <dawn/dawn_proc.h> | |
#include <dawn/dawn_wsi.h> | |
#include <dawn_wire/WireClient.h> | |
#include <dawn_wire/WireServer.h> | |
#include <dawn_native/DawnNative.h> | |
#include <iostream> | |
#include <thread> | |
#include <unistd.h> // pipe | |
#include <sys/socket.h> | |
#include <sys/un.h> | |
#include <fcntl.h> // F_GETFL, O_NONBLOCK etc | |
// silence "mangled name of 'ev_set_allocator' will change in C++17" | |
_Pragma("GCC diagnostic push") | |
_Pragma("GCC diagnostic ignored \"-Wc++17-compat-mangling\"") | |
#include <ev.h> | |
_Pragma("GCC diagnostic pop") | |
typedef struct ev_loop RunLoop; | |
#define DLOG_PREFIX "\e[1;34m[server]\e[0m " | |
#ifdef DEBUG | |
#define dlog(format, ...) ({ \ | |
fprintf(stderr, DLOG_PREFIX format " \e[2m(%s %d)\e[0m\n", \ | |
##__VA_ARGS__, __FUNCTION__, __LINE__); \ | |
fflush(stderr); \ | |
}) | |
#define errlog(format, ...) \ | |
(({ fprintf(stderr, "E " format " (%s:%d)\n", ##__VA_ARGS__, __FILE__, __LINE__); \ | |
fflush(stderr); })) | |
#else | |
#define dlog(...) do{}while(0) | |
#define errlog(format, ...) \ | |
(({ fprintf(stderr, "E " format "\n", ##__VA_ARGS__); fflush(stderr); })) | |
#endif | |
// backendType | |
// Default to D3D12, Metal, Vulkan, OpenGL in that order as D3D12 and Metal are the preferred on | |
// their respective platforms, and Vulkan is preferred to OpenGL | |
#if defined(DAWN_ENABLE_BACKEND_D3D12) | |
static wgpu::BackendType backendType = wgpu::BackendType::D3D12; | |
#elif defined(DAWN_ENABLE_BACKEND_METAL) | |
static wgpu::BackendType backendType = wgpu::BackendType::Metal; | |
#elif defined(DAWN_ENABLE_BACKEND_VULKAN) | |
static wgpu::BackendType backendType = wgpu::BackendType::Vulkan; | |
#elif defined(DAWN_ENABLE_BACKEND_OPENGL) | |
static wgpu::BackendType backendType = wgpu::BackendType::OpenGL; | |
#else | |
# error | |
#endif | |
static bool FDSetNonBlock(int fd) { | |
#ifdef _WIN32 | |
unsigned long arg = 1; | |
ioctlsocket(_get_osfhandle(fd), FIONBIO, &arg); // from libev/ev.c | |
#else | |
int flags = fcntl(fd, F_GETFL); | |
if (flags < 0 || | |
fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0 || | |
fcntl(fd, F_SETFD, FD_CLOEXEC)) // FD_CLOEXEC for fork | |
{ | |
errno = EWOULDBLOCK; | |
return false; | |
} | |
#endif | |
return true; | |
} | |
int createUNIXSocket(const char* filename, sockaddr_un* addr) { | |
addr->sun_family = AF_UNIX; | |
auto filenameLen = strlen(filename); | |
if (filenameLen > sizeof(addr->sun_path)-1) { | |
errno = ENAMETOOLONG; | |
return -1; | |
} | |
memcpy(addr->sun_path, filename, filenameLen+1); | |
return socket(AF_UNIX, SOCK_STREAM, 0); | |
} | |
int createUNIXSocketServer(const char* filename) { | |
/*struct*/ sockaddr_un addr; | |
int fd = createUNIXSocket(filename, &addr); | |
if (fd > -1) { | |
unlink(filename); | |
int acceptQueueSize = 5; | |
if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1 || | |
listen(fd, acceptQueueSize) == -1) | |
{ | |
int e = errno; | |
close(fd); | |
unlink(filename); | |
errno = e; | |
fd = -1; | |
} | |
} | |
return fd; | |
} | |
int connectUNIXSocket(const char* filename) { | |
/*struct*/ sockaddr_un addr; | |
int fd = createUNIXSocket(filename, &addr); | |
if (fd > -1) { | |
if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { | |
int e = errno; | |
close(fd); | |
errno = e; | |
fd = -1; | |
} | |
} | |
return fd; | |
} | |
enum class CmdBufType { | |
None, | |
Terrible, | |
}; | |
#define COMMAND_BUFFER_SIZE 4096*4 | |
class LolCommandBuffer : public dawn_wire::CommandSerializer { | |
dawn_wire::CommandHandler* mHandler = nullptr; | |
size_t mOffset = 0; | |
char mBuffer[COMMAND_BUFFER_SIZE]; | |
const char* mName = ""; | |
public: | |
int w = -1; // file descriptor to write to | |
LolCommandBuffer(const char* name) : mName(name) {} | |
LolCommandBuffer(dawn_wire::CommandHandler* handler) : mHandler(handler) {} | |
void SetHandler(dawn_wire::CommandHandler* handler) { mHandler = handler; } | |
size_t GetMaximumAllocationSize() const override { | |
return sizeof(mBuffer); | |
} | |
void* GetCmdSpace(size_t size) override { | |
assert(size <= sizeof(mBuffer)); | |
char* result = &mBuffer[mOffset]; | |
if (sizeof(mBuffer) - size < mOffset) { | |
if (!Flush()) | |
return nullptr; | |
return GetCmdSpace(size); | |
} | |
mOffset += size; | |
return result; | |
} | |
bool Flush() override { | |
if (mOffset == 0) | |
return true; | |
bool success = true; | |
success = mHandler->HandleCommands(mBuffer, mOffset) != nullptr; | |
if (!success) { | |
dlog("cmd buffer %s HandleCommands (%zu) FAILED", mName, mOffset); | |
} else { | |
dlog("cmd buffer %s HandleCommands (%zu) OK", mName, mOffset); | |
} | |
if (w != -1) { | |
dlog("cmd buffer %s Flush write %zu bytes", mName, mOffset); | |
ssize_t z = ::write(w, mBuffer, mOffset); | |
if (size_t(z) != mOffset) { | |
perror("cmd buffer Flush write"); | |
success = false; | |
} | |
} | |
//else { | |
// dlog("cmd buffer %s Flush skipping write since w=-1", mName); | |
//} | |
mOffset = 0; | |
return success; | |
} | |
}; | |
static CmdBufType cmdBufType = CmdBufType::Terrible; | |
static std::unique_ptr<dawn_native::Instance> instance; | |
static utils::BackendBinding* binding = nullptr; | |
static GLFWwindow* window = nullptr; | |
static dawn_wire::WireServer* wireServer = nullptr; | |
static dawn_wire::WireClient* wireClient = nullptr; | |
static LolCommandBuffer* c2sBuf = nullptr; | |
static LolCommandBuffer* s2cBuf = nullptr; | |
float uiScale = 1.0; | |
static WGPUDevice device; | |
static WGPUQueue queue; | |
static WGPUSwapChain swapchain; | |
static WGPURenderPipeline pipeline; | |
static WGPUTextureFormat swapChainFormat; | |
static void PrintDeviceError(WGPUErrorType errorType, const char* message, void*) { | |
const char* errorTypeName = ""; | |
switch (errorType) { | |
case WGPUErrorType_Validation: | |
errorTypeName = "Validation"; | |
break; | |
case WGPUErrorType_OutOfMemory: | |
errorTypeName = "Out of memory"; | |
break; | |
case WGPUErrorType_Unknown: | |
errorTypeName = "Unknown"; | |
break; | |
case WGPUErrorType_DeviceLost: | |
errorTypeName = "Device lost"; | |
break; | |
default: | |
UNREACHABLE(); | |
return; | |
} | |
std::cerr << "device error: " << errorTypeName << " error: " << message << std::endl; | |
} | |
static void PrintGLFWError(int code, const char* message) { | |
std::cerr << "GLFW error: " << code << " - " << message << std::endl; | |
} | |
wgpu::Device CreateCppDawnDevice2() { | |
//if (GetEnvironmentVar("ANGLE_DEFAULT_PLATFORM").empty()) { | |
// SetEnvironmentVar("ANGLE_DEFAULT_PLATFORM", "swiftshader"); | |
//} | |
glfwSetErrorCallback(PrintGLFWError); | |
if (!glfwInit()) { | |
return wgpu::Device(); | |
} | |
// Create the test window and discover adapters using it (esp. for OpenGL) | |
utils::SetupGLFWWindowHintsForBackend(backendType); | |
glfwWindowHint(GLFW_COCOA_RETINA_FRAMEBUFFER, GLFW_FALSE); | |
GLFWmonitor* monitor = nullptr; | |
window = glfwCreateWindow(640, 480, "hello-wire", monitor, nullptr); | |
if (!window) | |
return wgpu::Device(); | |
// read window UI scale from OS | |
float yscale = 0.0; // ignored | |
glfwGetWindowContentScale(window, &uiScale, &yscale); | |
// [rsms] move window to bottom right corner of screen | |
// glfwSetWindowPos(window, 1920, 960); | |
glfwSetWindowPos(window, 2560, 960); // 2nd screen, bottom left corner | |
instance = std::make_unique<dawn_native::Instance>(); | |
utils::DiscoverAdapter(instance.get(), window, backendType); | |
// Get an adapter for the backend to use, and create the device. | |
dawn_native::Adapter backendAdapter; | |
{ | |
std::vector<dawn_native::Adapter> adapters = instance->GetAdapters(); | |
auto adapterIt = std::find_if(adapters.begin(), adapters.end(), | |
[](const dawn_native::Adapter adapter) -> bool { | |
wgpu::AdapterProperties properties; | |
adapter.GetProperties(&properties); | |
return properties.backendType == backendType; | |
}); | |
ASSERT(adapterIt != adapters.end()); | |
backendAdapter = *adapterIt; | |
} | |
WGPUDevice backendDevice = backendAdapter.CreateDevice(); | |
DawnProcTable backendProcs = dawn_native::GetProcs(); | |
binding = utils::CreateBinding(backendType, window, backendDevice); | |
if (binding == nullptr) { | |
return wgpu::Device(); | |
} | |
// Choose whether to use the backend procs and devices directly, or set up the wire. | |
WGPUDevice cDevice = nullptr; | |
DawnProcTable procs; | |
switch (cmdBufType) { | |
case CmdBufType::None: | |
procs = backendProcs; | |
cDevice = backendDevice; | |
break; | |
case CmdBufType::Terrible: { | |
c2sBuf = new LolCommandBuffer("c2s"); | |
s2cBuf = new LolCommandBuffer("s2c"); | |
dawn_wire::WireServerDescriptor serverDesc = {}; | |
serverDesc.procs = &backendProcs; | |
serverDesc.serializer = s2cBuf; | |
wireServer = new dawn_wire::WireServer(serverDesc); | |
c2sBuf->SetHandler(wireServer); | |
dawn_wire::WireClientDescriptor clientDesc = {}; | |
clientDesc.serializer = c2sBuf; | |
wireClient = new dawn_wire::WireClient(clientDesc); | |
s2cBuf->SetHandler(wireClient); | |
procs = dawn_wire::client::GetProcs(); | |
auto devres = wireClient->ReserveDevice(); | |
wireServer->InjectDevice(backendDevice, devres.id, devres.generation); | |
cDevice = devres.device; | |
} break; | |
} | |
dawnProcSetProcs(&procs); | |
procs.deviceSetUncapturedErrorCallback(cDevice, PrintDeviceError, nullptr); | |
return wgpu::Device::Acquire(cDevice); | |
} | |
void flushWireBuffers() { | |
if (cmdBufType == CmdBufType::Terrible) { | |
bool s2cSuccess = s2cBuf->Flush(); | |
ASSERT(s2cSuccess); | |
} | |
} | |
wgpu::TextureFormat GetPreferredSwapChainTextureFormat2() { | |
flushWireBuffers(); | |
return static_cast<wgpu::TextureFormat>(binding->GetPreferredSwapChainTextureFormat()); | |
} | |
void configureSwapchain(int width, int height) { | |
WGPUSwapChainDescriptor descriptor = {}; | |
descriptor.implementation = binding->GetSwapChainImplementation(); | |
swapchain = wgpuDeviceCreateSwapChain(device, nullptr, &descriptor); | |
swapChainFormat = static_cast<WGPUTextureFormat>(GetPreferredSwapChainTextureFormat2()); | |
wgpuSwapChainConfigure(swapchain, swapChainFormat, WGPUTextureUsage_RenderAttachment, | |
width, height); | |
} | |
void init_dawn() { | |
// device = CreateCppDawnDevice().Release(); | |
device = CreateCppDawnDevice2().Release(); | |
queue = wgpuDeviceGetQueue(device); | |
int width_px = 100; | |
int height_px = 100; | |
glfwGetFramebufferSize(window, &width_px, &height_px); | |
configureSwapchain(width_px, height_px); | |
const char* vs = | |
"[[builtin(vertex_index)]] var<in> VertexIndex : u32;\n" | |
"[[builtin(position)]] var<out> Position : vec4<f32>;\n" | |
"const pos : array<vec2<f32>, 3> = array<vec2<f32>, 3>(\n" | |
" vec2<f32>( 0.0, 0.5),\n" | |
" vec2<f32>(-0.5, -0.5),\n" | |
" vec2<f32>( 0.5, -0.5)\n" | |
");\n" | |
"[[stage(vertex)]] fn main() -> void {\n" | |
" Position = vec4<f32>(pos[VertexIndex], 0.0, 1.0);\n" | |
" return;\n" | |
"}\n"; | |
WGPUShaderModule vsModule = utils::CreateShaderModule(device, vs).Release(); | |
const char* fs = | |
"[[location(0)]] var<out> fragColor : vec4<f32>;\n" | |
"[[stage(fragment)]] fn main() -> void {\n" | |
" fragColor = vec4<f32>(1.0, 0.0, 0.7, 1.0);\n" | |
" return;\n" | |
"}\n"; | |
WGPUShaderModule fsModule = utils::CreateShaderModule(device, fs).Release(); | |
{ | |
WGPURenderPipelineDescriptor2 descriptor = {}; | |
// Fragment state | |
WGPUBlendState blend = {}; | |
blend.color.operation = WGPUBlendOperation_Add; | |
blend.color.srcFactor = WGPUBlendFactor_One; | |
blend.color.dstFactor = WGPUBlendFactor_One; | |
blend.alpha.operation = WGPUBlendOperation_Add; | |
blend.alpha.srcFactor = WGPUBlendFactor_One; | |
blend.alpha.dstFactor = WGPUBlendFactor_One; | |
WGPUColorTargetState colorTarget = {}; | |
colorTarget.format = swapChainFormat; | |
colorTarget.blend = &blend; | |
colorTarget.writeMask = WGPUColorWriteMask_All; | |
WGPUFragmentState fragment = {}; | |
fragment.module = fsModule; | |
fragment.entryPoint = "main"; | |
fragment.targetCount = 1; | |
fragment.targets = &colorTarget; | |
descriptor.fragment = &fragment; | |
// Other state | |
descriptor.layout = nullptr; | |
descriptor.depthStencil = nullptr; | |
descriptor.vertex.module = vsModule; | |
descriptor.vertex.entryPoint = "main"; | |
descriptor.vertex.bufferCount = 0; | |
descriptor.vertex.buffers = nullptr; | |
descriptor.multisample.count = 1; | |
descriptor.multisample.mask = 0xFFFFFFFF; | |
descriptor.multisample.alphaToCoverageEnabled = false; | |
descriptor.primitive.frontFace = WGPUFrontFace_CCW; | |
descriptor.primitive.cullMode = WGPUCullMode_None; | |
descriptor.primitive.topology = WGPUPrimitiveTopology_TriangleList; | |
descriptor.primitive.stripIndexFormat = WGPUIndexFormat_Undefined; | |
pipeline = wgpuDeviceCreateRenderPipeline2(device, &descriptor); | |
} | |
wgpuShaderModuleRelease(vsModule); | |
wgpuShaderModuleRelease(fsModule); | |
} | |
bool animate = false; | |
// windowOnKeyPress is called when keyboard keys are pressed. | |
// window The window that received the event. | |
// key The keyboard key that was pressed or released. (GLFW_KEY_*) | |
// scancode The system-specific scancode of the key. | |
// action One of: GLFW_PRESS, GLFW_RELEASE, GLFW_REPEAT | |
// mods Bit field describing which modifier keys were held down. (GLFW_MOD_*) | |
// | |
void windowOnKeyPress(GLFWwindow* window, int key, int scancode, int action, int mods) { | |
if (action != GLFW_PRESS) | |
return; | |
printf("key press #%d %s\n", key, glfwGetKeyName(key, scancode)); | |
switch (key) { | |
case GLFW_KEY_A: | |
animate = !animate; | |
break; | |
default: | |
break; | |
} | |
} | |
void frame(); | |
// onWindowFramebufferResize is called when a window's framebuffer has changed size | |
// window The window which framebuffer was resized. | |
// width The new width, in pixels, of the framebuffer. | |
// height The new height, in pixels, of the framebuffer. | |
void onWindowFramebufferResize(GLFWwindow* window, int width, int height) { | |
//printf("onWindowFramebufferResize width=%d, height=%d\n", width, height); | |
//configureSwapchain(width, height); | |
} | |
// onWindowResize is called when a window has been resized | |
// window The window that was resized. | |
// width The new width, in screen coordinates, of the window. | |
// height The new height, in screen coordinates, of the window. | |
// Note: onWindowResize is called after any call to onWindowFramebufferResize | |
void onWindowResize(GLFWwindow* window, int width, int height) { | |
//printf("onWindowResize width=%d, height=%d\n", width, height); | |
// redraw as onWindowFramebufferResize might have replaced the swapchain | |
// frame(); | |
} | |
// Client is a connection to a remote client (from the server's perspective) | |
struct Client { | |
uint32_t id; | |
RunLoop* rl; | |
ev_io io; | |
bool shutdown = false; // true if shutting down | |
struct { | |
char* p[COMMAND_BUFFER_SIZE]; | |
size_t len = 0; | |
} wbuf; | |
int fd() { return io.fd; } | |
bool write(const void* ptr, size_t len) { | |
if (len > 0) { | |
if (shutdown || len > COMMAND_BUFFER_SIZE - wbuf.len) | |
return false; | |
memcpy(&wbuf.p[wbuf.len], ptr, len); | |
wbuf.len += len; | |
dlog("wrote %zu bytes to client connection", len); | |
if ((io.events & EV_WRITE) == 0) | |
ev_io_modify(&io, io.events | EV_WRITE); | |
} | |
return true; | |
} | |
void close() { | |
if (rl != nullptr) { | |
ev_io_stop(rl, &io); | |
rl = nullptr; | |
} | |
if (io.fd != -1) { | |
::close(io.fd); | |
io.fd = -1; | |
} | |
} | |
}; | |
const char* sockfile = "server.sock"; | |
Client* server_client0 = nullptr; | |
// server_fd_cb is called when a server's connection to a client has available I/O | |
static void server_client_fd_cb(RunLoop* rl, ev_io* w, int revents) { | |
Client* client = (Client*)w->data; | |
// dlog("server_client_fd_cb %s %s", | |
// revents & EV_READ ? "EV_READ" : "", | |
// revents & EV_WRITE ? "EV_WRITE" : ""); | |
int fd = client->fd(); | |
if (revents & EV_READ) { | |
char rbuf[COMMAND_BUFFER_SIZE]; | |
ssize_t n = ::read(fd, rbuf, sizeof(rbuf)); | |
//dlog("read %zd bytes", n); | |
if (n == 0) { | |
dlog("client#%u gone", client->id); | |
client->close(); | |
// delete client; | |
// if (client == server_client0) | |
// server_client0 = nullptr; | |
return; | |
} | |
// handle incoming data from client | |
if (wireServer->HandleCommands(rbuf, (size_t)n) == nullptr) | |
dlog("wireServer->HandleCommands FAILED"); | |
} | |
if (revents & EV_WRITE) { | |
auto& b = client->wbuf; | |
if (b.len != 0) { | |
ssize_t z = ::write(fd, &b.p[b.len], b.len); | |
dlog("server_client_fd_cb write(%zu) => %zd", b.len, z); | |
if (z < b.len) { | |
// shift remaining to 0 | |
size_t len2 = b.len - size_t(z); | |
memcpy(b.p, &b.p[b.len], len2); | |
b.len = len2; | |
} else { | |
b.len = 0; | |
} | |
} | |
if (b.len == 0) { | |
// nothing to write; stop requesting EV_WRITE | |
ev_io_stop(rl, w); | |
ev_io_modify(w, w->events & ~EV_WRITE); | |
ev_io_start(rl, w); | |
} | |
} | |
} | |
// server_fd_cb is called when data is readable, i.e. when a connection is awaiting accept | |
static void server_fd_cb(RunLoop* rl, ev_io* w, int revents) { | |
dlog("server_fd_cb called"); | |
int fd = accept(w->fd, NULL, NULL); | |
if (fd < 0) { | |
if (errno != EAGAIN) | |
perror("accept"); | |
return; | |
} | |
FDSetNonBlock(fd); | |
Client* client = new Client(); | |
server_client0 = client; | |
static uint32_t clientIdGen = 0; | |
client->id = clientIdGen++; | |
client->rl = rl; | |
client->io.data = (void*)client; | |
dlog("accepted new connection client#%u [fd %d]", client->id, fd); | |
//s2cBuf->w = fd; | |
ev_io_init(&client->io, server_client_fd_cb, fd, EV_READ); | |
ev_io_start(rl, &client->io); | |
// send welcome message | |
client->write("OHAI\n", 5); | |
// close(fd); | |
} | |
// another callback, this time for a time-out | |
static void server_poll_timeout_cb(RunLoop* rl, ev_timer* w, int revents) { | |
// dlog("poll timeout"); | |
// w->repeat = 2.0; | |
// ev_timer_again(rl, w); | |
// ev_timer_stop(rl, w); | |
} | |
void server_runloop(int fd) { | |
dlog("main start"); | |
RunLoop* rl = EV_DEFAULT; | |
FDSetNonBlock(fd); | |
ev_io server_fd_watcher; | |
ev_io_init(&server_fd_watcher, server_fd_cb, fd, EV_READ); | |
ev_io_start(rl, &server_fd_watcher); | |
ev_timer timeout_w; | |
ev_init(&timeout_w, server_poll_timeout_cb); | |
const uint32_t FPS = 5; | |
// double frameTimeGoal = 1.0 / 60.0; | |
double frameTimeGoal = 1.0 / (double)FPS; | |
timeout_w.repeat = frameTimeGoal; | |
ev_timer_again(rl, &timeout_w); | |
ev_unref(rl); // don't allow timer to keep runloop alive alone | |
const uint32_t frameTimingsSize = FPS; | |
double frameTimings[2][frameTimingsSize] = {{0}}; | |
uint32_t frameCounter = 0; | |
// for some reason we need to do this once for things to work... why? | |
if (!c2sBuf->Flush()) | |
dlog("c2sBuf->Flush failed"); | |
// forever | |
while (!glfwWindowShouldClose(window) /*&& frameCounter < 10*/) { | |
// dlog("frame %u", frameCounter); | |
double t1 = glfwGetTime(); | |
// if (server_client0) | |
// server_client0->write("SYNC\n", 5); | |
// frame(); | |
//if (!c2sBuf->Flush()) | |
// dlog("c2sBuf->Flush failed"); | |
// dlog("frame %u", frameCounter); | |
// bool s2cSuccess = s2cBuf->Flush(); | |
// assert(s2cSuccess); | |
double frameTime0 = glfwGetTime() - t1; | |
glfwPollEvents(); | |
timeout_w.repeat = frameTimeGoal - (glfwGetTime() - t1); | |
if (timeout_w.repeat > 0.0) { | |
ev_timer_again(rl, &timeout_w); | |
ev_run(rl, EVRUN_ONCE); | |
} else { | |
ev_timer_stop(rl, &timeout_w); | |
ev_run(rl, EVRUN_NOWAIT); | |
} | |
double frameTime1 = glfwGetTime() - t1; | |
// update stats | |
frameTimings[0][frameCounter % frameTimingsSize] = frameTime0; | |
frameTimings[1][frameCounter % frameTimingsSize] = frameTime1; | |
frameCounter++; | |
if ((frameCounter % frameTimingsSize) == 0) { | |
double frameTimingsAvg[2] = {0.0, 0.0}; | |
for (uint32_t i = 0; i < frameTimingsSize; i++) { | |
frameTimingsAvg[0] += frameTimings[0][i]; | |
frameTimingsAvg[1] += frameTimings[1][i]; | |
} | |
frameTimingsAvg[0] = frameTimingsAvg[0] / (double)frameTimingsSize; | |
frameTimingsAvg[1] = frameTimingsAvg[1] / (double)frameTimingsSize; | |
dlog("render %.2f ms %.0f FPS (%.2f ms/frame)", | |
frameTimingsAvg[0] * 1000.0, | |
1 / frameTimingsAvg[1], | |
frameTimingsAvg[1] * 1000.0); | |
} | |
} | |
dlog("exit"); | |
if (server_client0) | |
server_client0->close(); | |
close(fd); | |
} | |
int main(int argc, const char* argv[]) { | |
// create socket server | |
dlog("starting UNIX socket server \"%s\"", sockfile); | |
int server_fd = createUNIXSocketServer(sockfile); | |
if (server_fd < 0) { | |
perror("createUNIXSocketServer"); | |
return 1; | |
} | |
// init dawn | |
init_dawn(); | |
glfwSetKeyCallback(window, windowOnKeyPress); | |
glfwSetFramebufferSizeCallback(window, onWindowFramebufferResize); | |
glfwSetWindowSizeCallback(window, onWindowResize); | |
// run server on the this current thread | |
server_runloop(server_fd); | |
unlink(sockfile); | |
} | |
// // server read loop | |
// char buf[256]; | |
// while (1) { | |
// printf("server calling read(server.r) ...\n"); | |
// int r = ::read(server.r, buf, 256); | |
// printf("server read() => %d\n", r); | |
// if (r < 1) { | |
// if (r == -1) | |
// perror("server read"); | |
// // I/O closed; exit client | |
// break; | |
// } | |
// } | |
// Note: To update render size when window changes, poll window with glfwGetFramebufferSize | |
// and retrieve an updated swapchain via wgpuDeviceCreateSwapChain. | |
// See SyncFromWindow() in dawn/examples/ManualSwapChainTest.cpp for an implementation. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment