#define HSH_IMPLEMENTATION #include #include #include #include #include #include using namespace std::literals; #include "test-input.h" #include "urde-test.h" constexpr std::string_view AppName = "Hello Triangle"sv; #if !HSH_PROFILE_MODE #if _WIN32 #define NOMINMAX #include struct WsiConnection; struct WsiWindow { WsiConnection &Connection; HWND Window; explicit WsiWindow(WsiConnection &Connection); ~WsiWindow(); }; struct WsiConnection { HINSTANCE Instance; bool Running = true; operator HINSTANCE() const { return Instance; } static LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { if (msg == WM_DESTROY) PostQuitMessage(0); return DefWindowProc(hWnd, msg, wParam, lParam); } WsiConnection() { Instance = GetModuleHandle(nullptr); assert(Instance); WNDCLASSEX Class{}; Class.cbSize = sizeof(Class); Class.style = 0; Class.lpfnWndProc = &WindowProc; Class.cbClsExtra = 0; Class.cbWndExtra = 0; Class.hInstance = Instance; Class.hIcon = nullptr; Class.hCursor = nullptr; Class.hbrBackground = HBRUSH(GetStockObject(BLACK_BRUSH)); Class.lpszMenuName = nullptr; Class.lpszClassName = TEXT("TestappWndClass"); Class.hIconSm = nullptr; RegisterClassEx(&Class); } WsiWindow makeWindow() { return WsiWindow(*this); } void _handleMsg(const MSG &Msg) { switch (Msg.message) { case WM_QUIT: Running = false; break; default: TranslateMessage(&Msg); DispatchMessage(&Msg); break; } } void dispatchLatestEvents() { MSG Msg; while (PeekMessage(&Msg, nullptr, 0, 0, PM_REMOVE)) _handleMsg(Msg); } void runloop(const std::function &IdleFunc) { while (Running) { dispatchLatestEvents(); if (!IdleFunc()) { Running = false; break; } } } }; WsiWindow::WsiWindow(WsiConnection &Connection) : Connection(Connection) { RECT Rect{0, 0, 512, 512}; AdjustWindowRect(&Rect, WS_OVERLAPPEDWINDOW, false); Window = CreateWindow(TEXT("TestappWndClass"), TEXT("HSH Test App"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, Rect.right - Rect.left, Rect.bottom - Rect.top, (HWND) nullptr, (HMENU) nullptr, Connection, (LPVOID) nullptr); ShowWindow(Window, SW_SHOW); } WsiWindow::~WsiWindow() { DestroyWindow(Window); } struct PipelineCacheFileManager { static std::wstring GetFilename(const uint8_t UUID[VK_UUID_SIZE]) noexcept { std::wostringstream FileName; // TODO: make this more portable wchar_t LocalAppData[MAX_PATH]; if (GetEnvironmentVariable(TEXT("LOCALAPPDATA"), LocalAppData, MAX_PATH)) { FileName << LocalAppData; FileName << TEXT("/cache/hsh-test-pipeline-cache-"); for (int i = 0; i < VK_UUID_SIZE; ++i) FileName << std::hex << unsigned(UUID[i]); FileName << TEXT(".bin"); } return FileName.str(); } template static void ReadPipelineCache(Func F, const uint8_t UUID[VK_UUID_SIZE]) noexcept { if (std::FILE *File = ::_wfopen(GetFilename(UUID).c_str(), TEXT("rb"))) { std::fseek(File, 0, SEEK_END); auto Size = std::ftell(File); if (Size != 0) { std::fseek(File, 0, SEEK_SET); std::unique_ptr Data(new uint8_t[Size]); Size = std::fread(Data.get(), 1, Size, File); if (Size != 0) F(Data.get(), Size); } std::fclose(File); } } template static void WritePipelineCache(Func F, const uint8_t UUID[VK_UUID_SIZE]) noexcept { if (std::FILE *File = ::_wfopen(GetFilename(UUID).c_str(), TEXT("wb"))) { F([File](const uint8_t *Data, std::size_t Size) { std::fwrite(Data, 1, Size, File); }); std::fclose(File); } } }; #elif defined(__APPLE__) #include #include @interface MetalView : NSView - (id)initWithFrame:(NSRect)frameRect; - (CALayer *)makeBackingLayer; @end @implementation MetalView - (id)initWithFrame:(NSRect)frameRect { self = [super initWithFrame:frameRect]; self.wantsLayer = YES; return self; } - (CALayer *)makeBackingLayer { CAMetalLayer *Layer = [CAMetalLayer layer]; Layer.pixelFormat = MTLPixelFormatBGRA8Unorm; return Layer; } - (BOOL)layer:(CALayer *)layer shouldInheritContentsScale:(CGFloat)newScale fromWindow:(NSWindow *)window { return YES; } @end @interface CocoaWindow : NSWindow - (id)init; @end @implementation CocoaWindow - (id)init { NSRect ContentRect = NSMakeRect(0, 0, 512, 512); self = [super initWithContentRect:ContentRect styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskResizable /*| NSWindowStyleMaskFullSizeContentView*/ backing:NSBackingStoreBuffered defer:NO]; self.contentView = [[MetalView alloc] initWithFrame:ContentRect]; return self; } @end struct WsiConnection; struct WsiWindow { WsiConnection &Connection; CocoaWindow *Window; explicit WsiWindow(WsiConnection &Connection); ~WsiWindow(); }; @interface Application : NSApplication @end @implementation Application - (id)init { self = [super init]; self.activationPolicy = NSApplicationActivationPolicyRegular; self.delegate = self; NSMenu *Menu = [NSMenu new]; NSMenuItem *MenuItem = [NSMenuItem new]; [Menu addItem:MenuItem]; NSMenu *AppMenu = [NSMenu new]; NSString *AppName = [[NSProcessInfo processInfo] processName]; NSString *QuitTitle = [@"Quit " stringByAppendingString:AppName]; NSMenuItem *QuitMenuItem = [[NSMenuItem alloc] initWithTitle:QuitTitle action:@selector(terminate:) keyEquivalent:@"q"]; [AppMenu addItem:QuitMenuItem]; [MenuItem setSubmenu:AppMenu]; self.mainMenu = Menu; return self; } - (BOOL)applicationShouldTerminateAfterLastWindowClosed: (NSApplication *)sender { return YES; } - (void)terminate:(id)sender { /* Use -stop in place of -terminate so -run returns */ [self stop:sender]; } @end struct WsiConnection { Application *App; operator NSApplication *() const { return App; } WsiConnection() { App = [Application sharedApplication]; } WsiWindow makeWindow() { return WsiWindow(*this); } const std::function *IdleFuncPtr = nullptr; void idle() { if (!(*IdleFuncPtr)()) [App stop:nil]; if (App.isRunning) dispatch_async_f(dispatch_get_main_queue(), this, dispatch_function_t(_idle)); } static void _idle(WsiConnection *Self) { Self->idle(); } void dispatchLatestEvents() { while (NSEvent *Event = [App nextEventMatchingMask:NSEventMaskAny untilDate:nullptr inMode:NSDefaultRunLoopMode dequeue:YES]) [App sendEvent:Event]; } void runloop(const std::function &IdleFunc) { IdleFuncPtr = &IdleFunc; dispatch_async_f(dispatch_get_main_queue(), this, dispatch_function_t(_idle)); [App run]; IdleFuncPtr = nullptr; } }; WsiWindow::WsiWindow(WsiConnection &Connection) : Connection(Connection) { Window = [CocoaWindow new]; [Window makeKeyAndOrderFront:nil]; } WsiWindow::~WsiWindow() { [Window close]; } struct PipelineCacheFileManager { static NSURL *GetFilename() noexcept { std::ostringstream FileName; // TODO: make this more portable if (const char *home = std::getenv("HOME")) { FileName << home; FileName << "/Library/Caches/hsh-test-pipeline-cache.bin"; } return [NSURL fileURLWithPath:@(FileName.str().c_str())]; } template static void ReadPipelineCache(Func F) noexcept { F(GetFilename()); } template static void WritePipelineCache(Func F) noexcept { F(GetFilename()); } }; #else struct WsiConnection; struct WsiWindow { WsiConnection &Connection; xcb_window_t Window; explicit WsiWindow(WsiConnection &Connection); ~WsiWindow(); }; struct WsiConnection { xcb_connection_t *Connection; xcb_atom_t wmDeleteWin, wmProtocols; bool Running = true; operator xcb_connection_t *() const { return Connection; } WsiConnection() { Connection = xcb_connect(nullptr, nullptr); assert(Connection); xcb_intern_atom_cookie_t wmDeleteCookie = xcb_intern_atom( Connection, 0, strlen("WM_DELETE_WINDOW"), "WM_DELETE_WINDOW"); xcb_intern_atom_cookie_t wmProtocolsCookie = xcb_intern_atom(Connection, 0, strlen("WM_PROTOCOLS"), "WM_PROTOCOLS"); xcb_intern_atom_reply_t *wmDeleteReply = xcb_intern_atom_reply(Connection, wmDeleteCookie, nullptr); xcb_intern_atom_reply_t *wmProtocolsReply = xcb_intern_atom_reply(Connection, wmProtocolsCookie, nullptr); wmDeleteWin = wmDeleteReply->atom; wmProtocols = wmProtocolsReply->atom; } WsiWindow makeWindow() { return WsiWindow(*this); } void _handleEvent(xcb_generic_event_t *event) { switch (event->response_type & ~0x80u) { case XCB_CLIENT_MESSAGE: { auto *cm = (xcb_client_message_event_t *)event; if (cm->data.data32[0] == wmDeleteWin) Running = false; break; } default: break; } } void dispatchLatestEvents() { while (xcb_generic_event_t *event = xcb_poll_for_event(Connection)) { _handleEvent(event); free(event); } } void runloop(const std::function &IdleFunc) { while (Running) { dispatchLatestEvents(); if (!IdleFunc()) { Running = false; break; } } } }; WsiWindow::WsiWindow(WsiConnection &Connection) : Connection(Connection) { const struct xcb_setup_t *Setup = xcb_get_setup(Connection); xcb_screen_iterator_t Screen = xcb_setup_roots_iterator(Setup); assert(Screen.rem); Window = xcb_generate_id(Connection); uint32_t EventMask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK; uint32_t ValueList[] = {Screen.data->black_pixel, 0}; xcb_create_window(Connection, XCB_COPY_FROM_PARENT, Window, Screen.data->root, 0, 0, 512, 512, 0, XCB_WINDOW_CLASS_INPUT_OUTPUT, Screen.data->root_visual, EventMask, ValueList); xcb_change_property(Connection, XCB_PROP_MODE_REPLACE, Window, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8, AppName.size(), AppName.data()); xcb_change_property(Connection, XCB_PROP_MODE_REPLACE, Window, Connection.wmProtocols, 4, 32, 1, &Connection.wmDeleteWin); xcb_map_window(Connection, Window); xcb_flush(Connection); } WsiWindow::~WsiWindow() { xcb_destroy_window(Connection, Window); } struct PipelineCacheFileManager { static std::string GetFilename(const uint8_t UUID[VK_UUID_SIZE]) noexcept { std::ostringstream FileName; // TODO: make this more portable if (const char *home = std::getenv("HOME")) { FileName << home; FileName << "/.cache/hsh-test-pipeline-cache-"; for (int i = 0; i < VK_UUID_SIZE; ++i) FileName << std::hex << unsigned(UUID[i]); FileName << ".bin"; } return FileName.str(); } template static void ReadPipelineCache(Func F, const uint8_t UUID[VK_UUID_SIZE]) noexcept { if (std::FILE *File = std::fopen(GetFilename(UUID).c_str(), "rb")) { std::fseek(File, 0, SEEK_END); auto Size = std::ftell(File); if (Size != 0) { std::fseek(File, 0, SEEK_SET); std::unique_ptr Data(new uint8_t[Size]); Size = std::fread(Data.get(), 1, Size, File); if (Size != 0) F(Data.get(), Size); } std::fclose(File); } } template static void WritePipelineCache(Func F, const uint8_t UUID[VK_UUID_SIZE]) noexcept { if (std::FILE *File = std::fopen(GetFilename(UUID).c_str(), "wb")) { F([File](const uint8_t *Data, std::size_t Size) { std::fwrite(Data, 1, Size, File); }); std::fclose(File); } } }; #endif #if _WIN32 int wmain(int argc, wchar_t **argv) { #else int main(int argc, char **argv) { #endif WsiConnection Connection; WsiWindow Window = Connection.makeWindow(); #ifndef __APPLE__ auto Instance = hsh::create_vulkan_instance( AppName.data(), 0, "test-engine", 0, [](vk::DebugUtilsMessageSeverityFlagBitsEXT messageSeverity, vk::DebugUtilsMessageTypeFlagBitsEXT messageTypes, const vk::DebugUtilsMessengerCallbackDataEXT &pCallbackData) { std::cerr << to_string(messageSeverity) << " " << to_string(messageTypes) << " " << pCallbackData.pMessage << "\n"; }); if (!Instance) return 1; Instance.enumerate_vulkan_devices( [](const vk::PhysicalDeviceProperties &Props, const vk::PhysicalDeviceDriverProperties &DriverProps) { std::cerr << "name: " << Props.deviceName << " type: " << vk::to_string(Props.deviceType) << "\n"; return false; }); auto PhysSurface = Instance.create_phys_surface(Connection, Window.Window); if (!PhysSurface) { std::cerr << "Unable to create PhysSurface\n"; return 1; } auto Device = Instance.enumerate_vulkan_devices( [&](const vk::PhysicalDeviceProperties &Props, const vk::PhysicalDeviceDriverProperties &DriverProps) { return true; }, *PhysSurface); if (!Device) { std::cerr << "No vulkan devices found\n"; return 1; } #else auto Instance = hsh::create_metal_instance( [](NSError *Err) { std::cerr << Err.localizedDescription.UTF8String; }); if (!Instance) return 1; Instance.enumerate_metal_devices( [](id Device, bool NativeForPhysSurface) { std::cerr << "name: " << Device.name.UTF8String << "\n"; return false; }); CAMetalLayer *PhysSurface = (CAMetalLayer *)Window.Window.contentView.layer; auto Device = Instance.enumerate_metal_devices( [&](id Device, bool NativeForPhysSurface) { return NativeForPhysSurface; }, PhysSurface); if (!Device) { std::cerr << "No metal devices found\n"; return 1; } #endif auto Surface = hsh::create_surface(std::move(PhysSurface)); if (!Surface) { std::cerr << "PhysSurface not compatible\n"; return 1; } auto RenderTexture = hsh::create_render_texture2d(Surface); { hsh::ShaderFileMapper SFM; if (!SFM.Good) { std::cerr << "Unable to map shader data\n"; return 1; } PipelineCacheFileManager PCFM; Device.build_pipelines(PCFM); } MyNS::Binding PipelineBind{}; MyNS::Binding PipelineTemplate1Bind{}; MyNS::Binding PipelineTemplate2Bind{}; MyNS::Binding PipelineTemplate3Bind{}; ModelResources ModRes{}; hsh::binding ModelBinding; hsh::uniform_fifo UFifo; hsh::vertex_fifo VFifo; std::size_t CurColor = 0; constexpr std::array Rainbow{{{1.f, 0.f, 0.f, 1.f}, {1.f, 0.5f, 0.f, 1.f}, {1.f, 1.f, 0.f, 1.f}, {0.f, 1.f, 0.f, 1.f}, {0.f, 1.f, 1.f, 1.f}, {0.f, 0.f, 1.f, 1.f}, {0.5f, 0.f, 1.f, 1.f}}}; Connection.runloop([&]() { Device.enter_draw_context([&]() { if (!UFifo) UFifo = hsh::create_uniform_fifo( 256 + sizeof( MyNS::UniformData)); // Maximum alignment by vulkan spec is 256 if (!VFifo) VFifo = hsh::create_vertex_fifo(sizeof(MyNS::MyFormat) * 3 * 2); if (Surface.acquire_next_image()) { Connection.dispatchLatestEvents(); RenderTexture.attach(); hsh::clear_attachments(); auto UFifoBinding = UFifo.map([&](MyNS::UniformData &UniData) { UniData = MyNS::UniformData{}; UniData.xf[0][0] = 1.f; UniData.xf[1][1] = 1.f; UniData.xf[2][2] = 1.f; UniData.xf[3][3] = 1.f; // UniData.color = Rainbow[CurColor]; UniData.color = hsh::float4(1.f, 1.f, 1.f, 1.f); }); auto VFifoBinding = VFifo.map(3, [&](MyNS::MyFormat *VertData) { VertData[0] = MyNS::MyFormat{ hsh::float3{-1.f, -1.f, 0.f}, {}, Rainbow[CurColor]}; VertData[1] = MyNS::MyFormat{hsh::float3{1.f, -1.f, 0.f}, {}, Rainbow[(CurColor + 1) % Rainbow.size()]}; VertData[2] = MyNS::MyFormat{hsh::float3{1.f, 1.f, 0.f}, {}, Rainbow[(CurColor + 2) % Rainbow.size()]}; }); // CurColor = (CurColor + 1) % Rainbow.size(); MyNS::BindPipeline(PipelineBind, UFifoBinding, VFifoBinding).draw(0, 3); RenderTexture.resolve_surface(Surface.get()); } }); return true; }); return 0; } #else int main(int argc, char **argv) { MyNS::BuildPipelineTemplated(false, MyNS::AM_Alpha); MyNS::BuildPipelineTemplated(false, MyNS::AM_NoAlpha); MyNS::BuildPipelineTemplated(true, MyNS::AM_NoAlpha); ModelInfo ModInfo = CreateModelInfo(); MaterialInfo MatInfo = CreateMaterialInfo(); ModelResources ModRes = CreateModelResources(); BindDrawModel(ModInfo, MatInfo, PT_Normal, ModRes); return 0; } #endif