diff options
| author | Yong He <yonghe@outlook.com> | 2021-01-06 12:58:57 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-01-06 12:58:57 -0800 |
| commit | 92636513abe72d2da0c45f0e2c1235415e0671c3 (patch) | |
| tree | 234136e9d89006df9d6775e8bcd07e91ae344af7 /tools/graphics-app-framework | |
| parent | 706d4f91e269d473c963d31792fb2c8320933c9b (diff) | |
Refactor GUI/Window utils out of gfx library (#1649)
Co-authored-by: Yong He <yhe@nvidia.com>
Diffstat (limited to 'tools/graphics-app-framework')
| -rw-r--r-- | tools/graphics-app-framework/gui.cpp | 415 | ||||
| -rw-r--r-- | tools/graphics-app-framework/gui.h | 28 | ||||
| -rw-r--r-- | tools/graphics-app-framework/model.cpp | 566 | ||||
| -rw-r--r-- | tools/graphics-app-framework/model.h | 77 | ||||
| -rw-r--r-- | tools/graphics-app-framework/vector-math.h | 15 | ||||
| -rw-r--r-- | tools/graphics-app-framework/window.h | 122 | ||||
| -rw-r--r-- | tools/graphics-app-framework/windows/win-window.cpp | 398 |
7 files changed, 1621 insertions, 0 deletions
diff --git a/tools/graphics-app-framework/gui.cpp b/tools/graphics-app-framework/gui.cpp new file mode 100644 index 000000000..8208bd606 --- /dev/null +++ b/tools/graphics-app-framework/gui.cpp @@ -0,0 +1,415 @@ +// gui.cpp +#include "gui.h" + +#ifdef _WIN32 +#include <windows.h> +#include "external/imgui/examples/imgui_impl_win32.h" +IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); +#endif + + +namespace gfx { + +#ifdef _WIN32 +LRESULT CALLBACK guiWindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + LRESULT handled = ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam); + if(handled) return handled; + ImGuiIO& io = ImGui::GetIO(); + + switch( msg ) + { + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + if(io.WantCaptureMouse) handled = 1; + break; + + case WM_KEYDOWN: + case WM_KEYUP: + if(io.WantCaptureKeyboard) handled = 1; + break; + } + + return handled; +} +void setNativeWindowHook(Window* window, WNDPROC proc); +#endif + + +GUI::GUI(Window* window, Renderer* inRenderer) + : renderer(inRenderer) +{ + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); + +#ifdef _WIN32 + ImGui_ImplWin32_Init(getPlatformWindowHandle(window)); + + setNativeWindowHook(window, &guiWindowProc); +#endif + + // Let's do the initialization work required for our graphics API + // abstraction layer, so that we can pipe all IMGUI rendering + // through the same interface as other work. + // + + static const char* shaderCode = + "cbuffer U { float4x4 mvp; }; \ + Texture2D t; \ + SamplerState s; \ + struct AssembledVertex { \ + float2 pos; \ + float2 uv; \ + float4 col; \ + }; \ + struct CoarseVertex { \ + float4 col; \ + float2 uv; \ + }; \ + struct VSOutput { \ + CoarseVertex cv : U; \ + float4 pos : SV_Position; \ + }; \ + void vertexMain( \ + AssembledVertex i : U, \ + out VSOutput o) \ + { \ + o.cv.col = i.col; \ + o.cv.uv = i.uv; \ + o.pos = mul(mvp, \ + float4(i.pos.xy, 0.f, 1.f)); \ + } \ + float4 fragmentMain( \ + CoarseVertex i : U) \ + : SV_target \ + { \ + return i.col * t.Sample(s, i.uv); \ + } \ + "; + + SlangSession* slangSession = spCreateSession(nullptr); + SlangCompileRequest* slangRequest = spCreateCompileRequest(slangSession); + + // TODO: These two lines need to change based on what the target graphics API + // is, so we need a way for a `Renderer` to pass back its prefeerred code + // format and profile name... + // + int targetIndex = spAddCodeGenTarget(slangRequest, SLANG_DXBC); + spSetTargetProfile(slangRequest, targetIndex, spFindProfile(slangSession, "sm_4_0")); + + int translationUnitIndex = spAddTranslationUnit(slangRequest, SLANG_SOURCE_LANGUAGE_SLANG, nullptr); + spAddTranslationUnitSourceString(slangRequest, translationUnitIndex, "gui.cpp.slang", shaderCode); + + char const* vertexEntryPointName = "vertexMain"; + char const* fragmentEntryPointName = "fragmentMain"; + int vertexIndex = spAddEntryPoint(slangRequest, translationUnitIndex, vertexEntryPointName, SLANG_STAGE_VERTEX); + int fragmentIndex = spAddEntryPoint(slangRequest, translationUnitIndex, fragmentEntryPointName, SLANG_STAGE_FRAGMENT); + + const SlangResult compileRes = spCompile(slangRequest); + if(auto diagnostics = spGetDiagnosticOutput(slangRequest)) + { + reportError("%s", diagnostics); + } + if(SLANG_FAILED(compileRes)) + { + spDestroyCompileRequest(slangRequest); + spDestroySession(slangSession); + assert(!"unexpected"); + return; + } + + ISlangBlob* vertexShaderBlob = nullptr; + spGetEntryPointCodeBlob(slangRequest, vertexIndex, 0, &vertexShaderBlob); + + ISlangBlob* fragmentShaderBlob = nullptr; + spGetEntryPointCodeBlob(slangRequest, fragmentIndex, 0, &fragmentShaderBlob); + + char const* vertexCode = (char const*) vertexShaderBlob->getBufferPointer(); + char const* vertexCodeEnd = vertexCode + vertexShaderBlob->getBufferSize(); + + char const* fragmentCode = (char const*) fragmentShaderBlob->getBufferPointer(); + char const* fragmentCodeEnd = fragmentCode + fragmentShaderBlob->getBufferSize(); + + spDestroyCompileRequest(slangRequest); + spDestroySession(slangSession); + + gfx::ShaderProgram::KernelDesc kernelDescs[] = + { + { gfx::StageType::Vertex, vertexCode, vertexCodeEnd }, + { gfx::StageType::Fragment, fragmentCode, fragmentCodeEnd }, + }; + + gfx::ShaderProgram::Desc programDesc; + programDesc.pipelineType = gfx::PipelineType::Graphics; + programDesc.kernels = &kernelDescs[0]; + programDesc.kernelCount = 2; + + auto program = renderer->createProgram(programDesc); + + vertexShaderBlob->release(); + fragmentShaderBlob->release(); + + InputElementDesc inputElements[] = { + {"U", 0, Format::RG_Float32, offsetof(ImDrawVert, pos) }, + {"U", 1, Format::RG_Float32, offsetof(ImDrawVert, uv) }, + {"U", 2, Format::RGBA_Unorm_UInt8, offsetof(ImDrawVert, col) }, + }; + auto inputLayout = renderer->createInputLayout( + &inputElements[0], + SLANG_COUNT_OF(inputElements)); + + // + + List<DescriptorSetLayout::SlotRangeDesc> descriptorSetRanges; + descriptorSetRanges.add(DescriptorSetLayout::SlotRangeDesc(DescriptorSlotType::UniformBuffer)); + descriptorSetRanges.add(DescriptorSetLayout::SlotRangeDesc(DescriptorSlotType::SampledImage)); + descriptorSetRanges.add(DescriptorSetLayout::SlotRangeDesc(DescriptorSlotType::Sampler)); + + DescriptorSetLayout::Desc descriptorSetLayoutDesc; + descriptorSetLayoutDesc.slotRangeCount = descriptorSetRanges.getCount(); + descriptorSetLayoutDesc.slotRanges = descriptorSetRanges.getBuffer(); + + descriptorSetLayout = renderer->createDescriptorSetLayout(descriptorSetLayoutDesc); + + List<PipelineLayout::DescriptorSetDesc> pipelineDescriptorSets; + pipelineDescriptorSets.add(PipelineLayout::DescriptorSetDesc(descriptorSetLayout)); + + PipelineLayout::Desc pipelineLayoutDesc; + pipelineLayoutDesc.descriptorSetCount = pipelineDescriptorSets.getCount(); + pipelineLayoutDesc.descriptorSets = pipelineDescriptorSets.getBuffer(); + pipelineLayoutDesc.renderTargetCount = 1; + + pipelineLayout = renderer->createPipelineLayout(pipelineLayoutDesc); + + TargetBlendDesc targetBlendDesc; + targetBlendDesc.color.srcFactor = BlendFactor::SrcAlpha; + targetBlendDesc.color.dstFactor = BlendFactor::InvSrcAlpha; + targetBlendDesc.alpha.srcFactor = BlendFactor::InvSrcAlpha; + targetBlendDesc.alpha.dstFactor = BlendFactor::Zero; + + GraphicsPipelineStateDesc pipelineDesc; + pipelineDesc.renderTargetCount = 1; + pipelineDesc.program = program; + pipelineDesc.pipelineLayout = pipelineLayout; + pipelineDesc.inputLayout = inputLayout; + pipelineDesc.blend.targets = &targetBlendDesc; + pipelineDesc.blend.targetCount = 1; + pipelineDesc.rasterizer.cullMode = CullMode::None; + + // Set up the pieces of fixed-function state that we care about + pipelineDesc.depthStencil.depthTestEnable = false; + + // TODO: need to set up blending state... + + pipelineState = renderer->createGraphicsPipelineState(pipelineDesc); + + // Initialize the texture atlas + unsigned char* pixels; + int width, height; + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); + + { + gfx::TextureResource::Desc desc; + desc.init2D(Resource::Type::Texture2D, Format::RGBA_Unorm_UInt8, width, height, 1); + desc.setDefaults(Resource::Usage::PixelShaderResource); + + + ptrdiff_t mipRowStrides[] = { ptrdiff_t(width * 4 * sizeof(unsigned char)) }; + void* subResourceData[] = { pixels }; + TextureResource::Data initData; + initData.mipRowStrides = mipRowStrides; + initData.numMips = 1; + initData.numSubResources = 1; + initData.subResources = subResourceData; + + auto texture = renderer->createTextureResource(Resource::Usage::PixelShaderResource, desc, &initData); + + gfx::ResourceView::Desc viewDesc; + viewDesc.format = desc.format; + viewDesc.type = ResourceView::Type::ShaderResource; + auto textureView = renderer->createTextureView(texture, viewDesc); + + io.Fonts->TexID = (void*) textureView.detach(); + } + + { + SamplerState::Desc desc; + samplerState = renderer->createSamplerState(desc); + } +} + + + +void GUI::beginFrame() +{ +#ifdef _WIN32 + ImGui_ImplWin32_NewFrame(); +#endif + ImGui::NewFrame(); +} + +void GUI::endFrame() +{ + ImGui::Render(); + + ImDrawData* draw_data = ImGui::GetDrawData(); + auto vertexCount = draw_data->TotalVtxCount; + auto indexCount = draw_data->TotalIdxCount; + int commandListCount = draw_data->CmdListsCount; + + if(!vertexCount) return; + if(!indexCount) return; + if(!commandListCount) return; + + // Allocate transient vertex/index buffers to hold the data for this frame. + + gfx::BufferResource::Desc vertexBufferDesc; + vertexBufferDesc.init(vertexCount * sizeof(ImDrawVert)); + vertexBufferDesc.setDefaults(Resource::Usage::VertexBuffer); + vertexBufferDesc.cpuAccessFlags = Resource::AccessFlag::Write; + auto vertexBuffer = renderer->createBufferResource( + Resource::Usage::VertexBuffer, + vertexBufferDesc); + + gfx::BufferResource::Desc indexBufferDesc; + indexBufferDesc.init(indexCount * sizeof(ImDrawIdx)); + indexBufferDesc.setDefaults(Resource::Usage::IndexBuffer); + indexBufferDesc.cpuAccessFlags = Resource::AccessFlag::Write; + auto indexBuffer = renderer->createBufferResource( + Resource::Usage::IndexBuffer, + indexBufferDesc); + + { + ImDrawVert* dstVertex = (ImDrawVert*) renderer->map(vertexBuffer, MapFlavor::WriteDiscard); + ImDrawIdx* dstIndex = (ImDrawIdx*) renderer->map(indexBuffer, MapFlavor::WriteDiscard); + + for(int ii = 0; ii < commandListCount; ++ii) + { + const ImDrawList* commandList = draw_data->CmdLists[ii]; + memcpy(dstVertex, commandList->VtxBuffer.Data, commandList->VtxBuffer.Size * sizeof(ImDrawVert)); + memcpy(dstIndex, commandList->IdxBuffer.Data, commandList->IdxBuffer.Size * sizeof(ImDrawIdx)); + dstVertex += commandList->VtxBuffer.Size; + dstIndex += commandList->IdxBuffer.Size; + } + + renderer->unmap(vertexBuffer); + renderer->unmap(indexBuffer); + } + + // Allocate a transient constant buffer for projection matrix + gfx::BufferResource::Desc constantBufferDesc; + constantBufferDesc.init(sizeof(glm::mat4x4)); + constantBufferDesc.setDefaults(Resource::Usage::ConstantBuffer); + constantBufferDesc.cpuAccessFlags = Resource::AccessFlag::Write; + auto constantBuffer = renderer->createBufferResource( + Resource::Usage::ConstantBuffer, + constantBufferDesc); + + { + glm::mat4x4* dstMVP = (glm::mat4x4*) renderer->map(constantBuffer, MapFlavor::WriteDiscard); + + float L = draw_data->DisplayPos.x; + float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x; + float T = draw_data->DisplayPos.y; + float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y; + float mvp[4][4] = + { + { 2.0f/(R-L), 0.0f, 0.0f, 0.0f }, + { 0.0f, 2.0f/(T-B), 0.0f, 0.0f }, + { 0.0f, 0.0f, 0.5f, 0.0f }, + { (R+L)/(L-R), (T+B)/(B-T), 0.5f, 1.0f }, + }; + memcpy(dstMVP, mvp, sizeof(mvp)); + + renderer->unmap(constantBuffer); + } + + gfx::Viewport viewport; + viewport.originX = 0; + viewport.originY = 0; + viewport.extentY = draw_data->DisplaySize.y; + viewport.extentX = draw_data->DisplaySize.x; + viewport.extentY = draw_data->DisplaySize.y; + viewport.minZ = 0; + viewport.maxZ = 1; + + renderer->setViewport(viewport); + + auto pipelineType = PipelineType::Graphics; + renderer->setPipelineState(pipelineType, pipelineState); + + renderer->setVertexBuffer(0, vertexBuffer, sizeof(ImDrawVert)); + renderer->setIndexBuffer(indexBuffer, sizeof(ImDrawIdx) == 2 ? Format::R_UInt16 : Format::R_UInt32); + renderer->setPrimitiveTopology(PrimitiveTopology::TriangleList); + + UInt vertexOffset = 0; + UInt indexOffset = 0; + ImVec2 pos = draw_data->DisplayPos; + for(int ii = 0; ii < commandListCount; ++ii) + { + auto commandList = draw_data->CmdLists[ii]; + auto commandCount = commandList->CmdBuffer.Size; + for(int jj = 0; jj < commandCount; jj++) + { + auto command = &commandList->CmdBuffer[jj]; + if(auto userCallback = command->UserCallback) + { + userCallback(commandList, command); + } + else + { + ScissorRect rect = + { + (Int)(command->ClipRect.x - pos.x), + (Int)(command->ClipRect.y - pos.y), + (Int)(command->ClipRect.z - pos.x), + (Int)(command->ClipRect.w - pos.y) + }; + renderer->setScissorRect(rect); + + // TODO: This should be a dynamic/transient descriptor set... + auto descriptorSet = renderer->createDescriptorSet(descriptorSetLayout); + descriptorSet->setConstantBuffer(0, 0, constantBuffer); + descriptorSet->setResource(1, 0, + (gfx::ResourceView*) command->TextureId); + descriptorSet->setSampler(2, 0, + samplerState); + + renderer->setDescriptorSet( + pipelineType, + pipelineLayout, + 0, + descriptorSet); + + renderer->drawIndexed(command->ElemCount, indexOffset, vertexOffset); + } + indexOffset += command->ElemCount; + } + vertexOffset += commandList->VtxBuffer.Size; + } +} + +GUI::~GUI() +{ + auto& io = ImGui::GetIO(); + + { + RefPtr<ResourceView> textureView; + textureView.attach((ResourceView*) io.Fonts->TexID); + textureView = nullptr; + } + +#ifdef _WIN32 + ImGui_ImplWin32_Shutdown(); +#endif + + ImGui::DestroyContext(); +} + +} // gfx + +#include "external/imgui/imgui.cpp" +#include "external/imgui/imgui_draw.cpp" +#ifdef _WIN32 +#include "external/imgui/examples/imgui_impl_win32.cpp" +#endif diff --git a/tools/graphics-app-framework/gui.h b/tools/graphics-app-framework/gui.h new file mode 100644 index 000000000..d2c52de3c --- /dev/null +++ b/tools/graphics-app-framework/gui.h @@ -0,0 +1,28 @@ +// gui.h +#pragma once + +#include "tools/gfx/render.h" +#include "vector-math.h" +#include "window.h" + +#include "external/imgui/imgui.h" + +namespace gfx { + +struct GUI : RefObject +{ + GUI(Window* window, Renderer* renderer); + ~GUI(); + + void beginFrame(); + void endFrame(); + +private: + RefPtr<Renderer> renderer; + RefPtr<PipelineState> pipelineState; + RefPtr<DescriptorSetLayout> descriptorSetLayout; + RefPtr<PipelineLayout> pipelineLayout; + RefPtr<SamplerState> samplerState; +}; + +} // gfx diff --git a/tools/graphics-app-framework/model.cpp b/tools/graphics-app-framework/model.cpp new file mode 100644 index 000000000..5d3a850e4 --- /dev/null +++ b/tools/graphics-app-framework/model.cpp @@ -0,0 +1,566 @@ +// model.cpp +#include "model.h" + +#include "window.h" + +#define TINYOBJLOADER_IMPLEMENTATION +#include "../../external/tinyobjloader/tiny_obj_loader.h" + +#define STB_IMAGE_IMPLEMENTATION +#include "../../external/stb/stb_image.h" + +#define STB_IMAGE_RESIZE_IMPLEMENTATION +#include "../../external/stb/stb_image_resize.h" + +#include "../../external/glm/glm/glm.hpp" +#include "../../external/glm/glm/gtc/matrix_transform.hpp" +#include "../../external/glm/glm/gtc/constants.hpp" + +#include <memory> +#include <unordered_map> +#include <unordered_set> + +namespace gfx { + +// TinyObj provides a tuple type that bundles up indices, but doesn't +// provide equality comparison or hashing for that type. We'd like +// to have a hash function so that we can unique indices. +// +// In the simplest case, we could define hashing and operator== operations +// directly on `tinobj::index_t`, but that would create problems if they +// revise their API. +// +// We will instead define our own wrapper type that supports equality +// comparisons. +// +struct ObjIndexKey +{ + tinyobj::index_t index; +}; + +bool operator==(ObjIndexKey const& left, ObjIndexKey const& right) +{ + return left.index.vertex_index == right.index.vertex_index + && left.index.normal_index == right.index.normal_index + && left.index.texcoord_index == right.index.texcoord_index; +} + +struct Hasher +{ + template<typename T> + void add(T const& v) + { + state ^= std::hash<T>()(v) + 0x9e3779b9 + (state << 6) + (state >> 2); + } + size_t state = 0; +}; + +struct SmoothingGroupVertexID +{ + size_t smoothingGroup; + size_t positionID; +}; +bool operator==(SmoothingGroupVertexID const& left, SmoothingGroupVertexID const& right) +{ + return left.smoothingGroup == right.smoothingGroup + && left.positionID == right.positionID; +} + +} + +namespace std +{ + template<> struct hash<gfx::ObjIndexKey> + { + size_t operator()(gfx::ObjIndexKey const& key) const + { + gfx::Hasher hasher; + hasher.add(key.index.vertex_index); + hasher.add(key.index.normal_index); + hasher.add(key.index.texcoord_index); + return hasher.state; + } + }; + + template<> struct hash<gfx::SmoothingGroupVertexID> + { + size_t operator()(gfx::SmoothingGroupVertexID const& id) const + { + gfx::Hasher hasher; + hasher.add(id.smoothingGroup); + hasher.add(id.positionID); + return hasher.state; + } + }; +} + +namespace gfx +{ + +RefPtr<TextureResource> loadTextureImage( + Renderer* renderer, + char const* path) +{ + int extentX = 0; + int extentY = 0; + int originalChannelCount = 0; + int requestedChannelCount = 4; // force to 4-component result + stbi_uc* data = stbi_load( + path, + &extentX, + &extentY, + &originalChannelCount, + requestedChannelCount); + if(!data) + return nullptr; + + int channelCount = requestedChannelCount ? requestedChannelCount : originalChannelCount; + + Format format; + switch(channelCount) + { + default: + return nullptr; + + case 4: format = Format::RGBA_Unorm_UInt8; + + // TODO: handle other cases here if/when we stop forcing 4-component + // results when loading the image with stb_image. + } + + std::vector<void*> subresourceInitData; + std::vector<ptrdiff_t> mipRowStrides; + + ptrdiff_t stride = extentX * channelCount * sizeof(stbi_uc); + + subresourceInitData.push_back(data); + mipRowStrides.push_back(stride); + + // create down-sampled images for the different mip levels + bool generateMips = true; + if(generateMips) + { + int prevExtentX = extentX; + int prevExtentY = extentY; + stbi_uc* prevData = data; + int prevStride = int(stride); + + for(;;) + { + if(prevExtentX == 1 && prevExtentY == 1) + break; + + int newExtentX = prevExtentX / 2; + int newExtentY = prevExtentY / 2; + + if(!newExtentX) newExtentX = 1; + if(!newExtentY) newExtentY = 1; + + stbi_uc* newData = (stbi_uc*) malloc(newExtentX * newExtentY * channelCount * sizeof(stbi_uc)); + int newStride = int(newExtentX * channelCount * sizeof(stbi_uc)); + + stbir_resize_uint8_srgb( + prevData, prevExtentX, prevExtentY, prevStride, + newData, newExtentX, newExtentY, newStride, + channelCount, + STBIR_ALPHA_CHANNEL_NONE, + STBIR_FLAG_ALPHA_PREMULTIPLIED); + + subresourceInitData.push_back(newData); + mipRowStrides.push_back(newStride); + + prevExtentX = newExtentX; + prevExtentY = newExtentY; + prevData = newData; + prevStride = newStride; + } + } + + int mipCount = (int) mipRowStrides.size(); + + TextureResource::Desc desc; + desc.init2D(Resource::Type::Texture2D, format, extentX, extentY, mipCount); + + TextureResource::Data initData; + initData.numSubResources = mipCount; + initData.numMips = mipCount; + initData.subResources = &subresourceInitData[0]; + initData.mipRowStrides = &mipRowStrides[0]; + + auto texture = renderer->createTextureResource( + Resource::Usage::PixelShaderResource, + desc, + &initData); + + free(data); + + return texture; +} + +static std::string makeString(const char* start, const char* end) +{ + return std::string(start, size_t(end - start)); +} + +Result ModelLoader::load( + char const* inputPath, + void** outModel) +{ + // TODO: need to actually allocate/load the data + + tinyobj::attrib_t objVertexAttributes; + std::vector<tinyobj::shape_t> objShapes; + std::vector<tinyobj::material_t> objMaterials; + + std::string baseDir; + if( auto lastSlash = strrchr(inputPath, '/') ) + { + baseDir = makeString(inputPath, lastSlash); + } + + std::string diagnostics; + bool shouldTriangulate = true; + bool success = tinyobj::LoadObj( + &objVertexAttributes, + &objShapes, + &objMaterials, + &diagnostics, + inputPath, + baseDir.size() ? baseDir.c_str() : nullptr, + shouldTriangulate); + + if(!diagnostics.empty()) + { + log("%s", diagnostics.c_str()); + } + if(!success) + { + return SLANG_FAIL; + } + + // Translate each material imported by TinyObj into a format that + // we can actually use for rendering. + // + std::vector<void*> materials; + for(auto& objMaterial : objMaterials) + { + MaterialData materialData; + + materialData.diffuseColor = glm::vec3( + objMaterial.diffuse[0], + objMaterial.diffuse[1], + objMaterial.diffuse[2]); + + materialData.specularColor = glm::vec3( + objMaterial.specular[0], + objMaterial.specular[1], + objMaterial.specular[2]); + + materialData.specularity = objMaterial.shininess; + + // load any referenced textures here + if(objMaterial.diffuse_texname.length()) + { + materialData.diffuseMap = loadTextureImage( + renderer, + objMaterial.diffuse_texname.c_str()); + } + + auto material = callbacks->createMaterial(materialData); + materials.push_back(material); + } + + // Flip the winding order on all faces if we are asked to... + // + if(loadFlags & LoadFlag::FlipWinding) + { + for(auto& objShape : objShapes) + { + size_t objIndexCounter = 0; + size_t objFaceCounter = 0; + for(auto objFaceVertexCount : objShape.mesh.num_face_vertices) + { + size_t beginIndex = objIndexCounter; + size_t endIndex = beginIndex + objFaceVertexCount; + objIndexCounter = endIndex; + + size_t halfCount = objFaceVertexCount / 2; + for(size_t ii = 0; ii < halfCount; ++ii) + { + std::swap( + objShape.mesh.indices[beginIndex + ii], + objShape.mesh.indices[endIndex - (ii + 1)]); + } + } + } + + } + + // Identify cases where a face has a vertex without a normal, and in that + // case remember that the given vertex needs to be "smoothed" as part of + // the smoothing group for that face. Note that it is possible for the + // same vertex (position) to be part of faces in distinct smoothing groups. + // + std::unordered_map<SmoothingGroupVertexID, size_t> smoothedVertexNormals; + size_t firstSmoothedNormalID = objVertexAttributes.normals.size() / 3; + size_t flatFaceCounter = 0; + for(auto& objShape : objShapes) + { + size_t objIndexCounter = 0; + size_t objFaceCounter = 0; + for(auto objFaceVertexCount : objShape.mesh.num_face_vertices) + { + const size_t flatFaceIndex = flatFaceCounter++; + const size_t objFaceIndex = objFaceCounter++; + size_t smoothingGroup = objShape.mesh.smoothing_group_ids[objFaceIndex]; + if(!smoothingGroup) + { + smoothingGroup = ~flatFaceIndex; + } + + for(size_t objFaceVertex = 0; objFaceVertex < objFaceVertexCount; ++objFaceVertex) + { + tinyobj::index_t& objIndex = objShape.mesh.indices[objIndexCounter++]; + + if(objIndex.normal_index < 0) + { + SmoothingGroupVertexID smoothVertexID; + smoothVertexID.positionID = objIndex.vertex_index; + smoothVertexID.smoothingGroup = smoothingGroup; + + if(smoothedVertexNormals.find(smoothVertexID) == smoothedVertexNormals.end()) + { + size_t normalID = objVertexAttributes.normals.size() / 3; + objVertexAttributes.normals.push_back(0); + objVertexAttributes.normals.push_back(0); + objVertexAttributes.normals.push_back(0); + + smoothedVertexNormals.insert(std::make_pair(smoothVertexID, normalID)); + + objIndex.normal_index = int(normalID); + } + } + } + } + } + // + // Having identified which vertices we need to smooth, we will make another + // pass to compute face normals and apply them to the vertices that belong + // to the same smoothing group. + // + flatFaceCounter = 0; + for(auto& objShape : objShapes) + { + size_t objIndexCounter = 0; + size_t objFaceCounter = 0; + for(auto objFaceVertexCount : objShape.mesh.num_face_vertices) + { + const size_t flatFaceIndex = flatFaceCounter++; + const size_t objFaceIndex = objFaceCounter++; + size_t smoothingGroup = objShape.mesh.smoothing_group_ids[objFaceIndex]; + if(!smoothingGroup) + { + smoothingGroup = ~flatFaceIndex; + } + + glm::vec3 faceNormal; + if(objFaceVertexCount >= 3) + { + glm::vec3 v[3]; + for(size_t objFaceVertex = 0; objFaceVertex < 3; ++objFaceVertex) + { + tinyobj::index_t objIndex = objShape.mesh.indices[objIndexCounter + objFaceVertex]; + if(objIndex.vertex_index >= 0) + { + v[objFaceVertex] = glm::vec3( + objVertexAttributes.vertices[3 * objIndex.vertex_index + 0], + objVertexAttributes.vertices[3 * objIndex.vertex_index + 1], + objVertexAttributes.vertices[3 * objIndex.vertex_index + 2]); + } + } + faceNormal = cross(v[1] - v[0], v[2] - v[0]); + } + + // Add this face normal to any to-be-smoothed vertex on the face. + for(size_t objFaceVertex = 0; objFaceVertex < objFaceVertexCount; ++objFaceVertex) + { + tinyobj::index_t objIndex = objShape.mesh.indices[objIndexCounter++]; + + SmoothingGroupVertexID smoothVertexID; + smoothVertexID.positionID = objIndex.vertex_index; + smoothVertexID.smoothingGroup = smoothingGroup; + + auto ii = smoothedVertexNormals.find(smoothVertexID); + if(ii != smoothedVertexNormals.end()) + { + size_t normalID = ii->second; + objVertexAttributes.normals[normalID * 3 + 0] += faceNormal.x; + objVertexAttributes.normals[normalID * 3 + 1] += faceNormal.y; + objVertexAttributes.normals[normalID * 3 + 2] += faceNormal.z; + } + } + } + } + // + // Once we've added all contributions from each smoothing group, + // we can normalize the normals to compute the area-weighted average. + // + size_t normalCount = objVertexAttributes.normals.size() / 3; + for(size_t ii = firstSmoothedNormalID; ii < normalCount; ++ii) + { + glm::vec3 normal = glm::vec3( + objVertexAttributes.normals[3 * ii + 0], + objVertexAttributes.normals[3 * ii + 1], + objVertexAttributes.normals[3 * ii + 2]); + + normal = normalize(normal); + + objVertexAttributes.normals[3 * ii + 0] = normal.x; + objVertexAttributes.normals[3 * ii + 1] = normal.y; + objVertexAttributes.normals[3 * ii + 2] = normal.z; + } + + // TODO: we should sort the faces to group faces with + // the same material ID together, in case they weren't + // grouped in the original file. + + // We need to undo the .obj indexing stuff so that we have + // standard position/normal/etc. data in a single flat array + + std::unordered_map<ObjIndexKey, Index> mapObjIndexToFlatIndex; + std::vector<Vertex> flatVertices; + std::vector<Index> flatIndices; + + MeshData* currentMesh = nullptr; + MeshData currentMeshStorage; + + std::vector<void*> meshes; + + void* defaultMaterial = nullptr; + + for(auto& objShape : objShapes) + { + size_t objIndexCounter = 0; + size_t objFaceCounter = 0; + for(auto objFaceVertexCount : objShape.mesh.num_face_vertices) + { + size_t objFaceIndex = objFaceCounter++; + int faceMaterialID = objShape.mesh.material_ids[objFaceIndex]; + void* faceMaterial = nullptr; + if( faceMaterialID < 0 ) + { + if( !defaultMaterial ) + { + MaterialData defaultMaterialData; + defaultMaterialData.diffuseColor = glm::vec3(0.5, 0.5, 0.5); + defaultMaterial = callbacks->createMaterial(defaultMaterialData); + } + faceMaterial = defaultMaterial; + } + else + { + faceMaterial = materials[faceMaterialID]; + } + + if(!currentMesh || (faceMaterial != currentMesh->material)) + { + // finish old mesh. + if(currentMesh) + { + meshes.push_back(callbacks->createMesh(*currentMesh)); + } + + // Need to start a new mesh. + currentMesh = ¤tMeshStorage; + currentMesh->material = faceMaterial; + currentMesh->firstIndex = (int)flatIndices.size(); + currentMesh->indexCount = 0; + } + + for(size_t objFaceVertex = 0; objFaceVertex < objFaceVertexCount; ++objFaceVertex) + { + tinyobj::index_t objIndex = objShape.mesh.indices[objIndexCounter++]; + ObjIndexKey objIndexKey; objIndexKey.index = objIndex; + + + Index flatIndex = Index(-1); + auto iter = mapObjIndexToFlatIndex.find(objIndexKey); + if(iter != mapObjIndexToFlatIndex.end()) + { + flatIndex = iter->second; + } + else + { + Vertex flatVertex; + if(objIndex.vertex_index >= 0) + { + flatVertex.position = scale * glm::vec3( + objVertexAttributes.vertices[3 * objIndex.vertex_index + 0], + objVertexAttributes.vertices[3 * objIndex.vertex_index + 1], + objVertexAttributes.vertices[3 * objIndex.vertex_index + 2]); + } + if(objIndex.normal_index >= 0) + { + flatVertex.normal = glm::vec3( + objVertexAttributes.normals[3 * objIndex.normal_index + 0], + objVertexAttributes.normals[3 * objIndex.normal_index + 1], + objVertexAttributes.normals[3 * objIndex.normal_index + 2]); + } + if(objIndex.texcoord_index >= 0) + { + flatVertex.uv = glm::vec2( + objVertexAttributes.texcoords[2 * objIndex.texcoord_index + 0], + objVertexAttributes.texcoords[2 * objIndex.texcoord_index + 1]); + } + + flatIndex = uint32_t(flatVertices.size()); + mapObjIndexToFlatIndex.insert(std::make_pair(objIndexKey, flatIndex)); + flatVertices.push_back(flatVertex); + } + + flatIndices.push_back(flatIndex); + currentMesh->indexCount++; + } + } + } + + // finish last mesh. + if(currentMesh) + { + meshes.push_back(callbacks->createMesh(*currentMesh)); + } + + ModelData modelData; + + modelData.vertexCount = (int)flatVertices.size(); + modelData.indexCount = (int)flatIndices.size(); + + modelData.meshCount = int(meshes.size()); + modelData.meshes = meshes.data(); + + BufferResource::Desc vertexBufferDesc; + vertexBufferDesc.init(modelData.vertexCount * sizeof(Vertex)); + vertexBufferDesc.setDefaults(Resource::Usage::VertexBuffer); + + modelData.vertexBuffer = renderer->createBufferResource( + Resource::Usage::VertexBuffer, + vertexBufferDesc, + flatVertices.data()); + if(!modelData.vertexBuffer) return SLANG_FAIL; + + BufferResource::Desc indexBufferDesc; + indexBufferDesc.init(modelData.indexCount * sizeof(Index)); + vertexBufferDesc.setDefaults(Resource::Usage::IndexBuffer); + + modelData.indexBuffer = renderer->createBufferResource( + Resource::Usage::IndexBuffer, + indexBufferDesc, + flatIndices.data()); + if(!modelData.indexBuffer) return SLANG_FAIL; + + *outModel = callbacks->createModel(modelData); + + return SLANG_OK; +} + +} // gfx diff --git a/tools/graphics-app-framework/model.h b/tools/graphics-app-framework/model.h new file mode 100644 index 000000000..e86940244 --- /dev/null +++ b/tools/graphics-app-framework/model.h @@ -0,0 +1,77 @@ +// model.h +#pragma once + +#include "tools/gfx/render.h" +#include "vector-math.h" + +#include <vector> +#include <string> + +namespace gfx { + +struct ModelLoader +{ + struct MaterialData + { + glm::vec3 diffuseColor; + glm::vec3 specularColor; + float specularity; + + RefPtr<TextureResource> diffuseMap; + }; + + struct Vertex + { + glm::vec3 position; + glm::vec3 normal; + glm::vec2 uv; + }; + + typedef uint32_t Index; + + struct MeshData + { + int firstIndex; + int indexCount; + + void* material; + }; + + struct ModelData + { + RefPtr<BufferResource> vertexBuffer; + RefPtr<BufferResource> indexBuffer; + PrimitiveTopology primitiveTopology; + int vertexCount; + int indexCount; + int meshCount; + void* const* meshes; + }; + + struct ICallbacks + { + typedef ModelLoader::MaterialData MaterialData; + typedef ModelLoader::MeshData MeshData; + typedef ModelLoader::ModelData ModelData; + + virtual void* createMaterial(MaterialData const& data) = 0; + virtual void* createMesh(MeshData const& data) = 0; + virtual void* createModel(ModelData const& data) = 0; + }; + + typedef uint32_t LoadFlags; + enum LoadFlag : LoadFlags + { + FlipWinding = 1 << 0, + }; + + ICallbacks* callbacks = nullptr; + RefPtr<Renderer> renderer; + LoadFlags loadFlags = 0; + float scale = 1.0f; + + Result load(char const* inputPath, void** outModel); +}; + + +} // gfx diff --git a/tools/graphics-app-framework/vector-math.h b/tools/graphics-app-framework/vector-math.h new file mode 100644 index 000000000..e35cb46ac --- /dev/null +++ b/tools/graphics-app-framework/vector-math.h @@ -0,0 +1,15 @@ +// vector-math.h +#pragma once + +// We will use the GLM library for our vector math types, just for simplicity. + +#include "../../external/glm/glm/glm.hpp" +#include "../../external/glm/glm/gtc/matrix_transform.hpp" +#include "../../external/glm/glm/gtc/constants.hpp" +#include "../../external/glm/glm/gtc/quaternion.hpp" + +namespace gfx { + +using namespace glm; + +} // gfx diff --git a/tools/graphics-app-framework/window.h b/tools/graphics-app-framework/window.h new file mode 100644 index 000000000..e6f886f42 --- /dev/null +++ b/tools/graphics-app-framework/window.h @@ -0,0 +1,122 @@ +// window.h +#pragma once + +#include <stdint.h> + +namespace gfx { + +struct Window; + +enum class KeyCode +{ + Unknown, + + // TODO: extend this to cover at least a standard US-English keyboard + + A, B, C, D, E, F, G, H, I, J, + K, L, M, N, O, P, Q, R, S, T, + U, V, W, X, Y, Z, + + Space, +}; + +enum class EventCode : uint32_t +{ + MouseDown, + MouseUp, + MouseMoved, + KeyDown, + KeyUp, +}; + +struct Event +{ + EventCode code; + Window* window; + union + { + struct + { + float x; + float y; + } mouse; + + KeyCode key; + } u; +}; + +typedef void (*EventHandler)(Event const&); + +struct WindowDesc +{ + char const* title = nullptr; + void* userData = nullptr; + int width = 0; + int height = 0; + EventHandler eventHandler = nullptr; +}; + +Window* createWindow(WindowDesc const& desc); +void showWindow(Window* window); + +void* getPlatformWindowHandle(Window* window); +void* getUserData(Window* window); + +/// Opaque state provided by platform for a running application. +typedef struct ApplicationContext ApplicationContext; + +/// User-defined application entry-point function. +typedef void(*ApplicationFunc)(ApplicationContext* context); + +/// Dispatch any pending events for application. +/// +/// @returns `true` if application should keep running. +bool dispatchEvents(ApplicationContext* context); + +/// Exit the application with a given result code +void exitApplication(ApplicationContext* context, int resultCode); + +/// Log a message to an appropriate logging destination. +void log(char const* message, ...); + +/// Report an error to an appropriate logging destination. +int reportError(char const* message, ...); + +uint64_t getCurrentTime(); + +uint64_t getTimerFrequency(); + +/// Run an application given the specified callback and command-line arguments. +int runApplication( + ApplicationFunc func, + int argc, + char const* const* argv); + +#define GFX_CONSOLE_MAIN(APPLICATION_ENTRY) \ + int main(int argc, char** argv) { \ + return gfx::runApplication(&(APPLICATION_ENTRY), argc, argv); \ + } + +#ifdef _WIN32 + +int runWindowsApplication( + ApplicationFunc func, + void* instance, + int showCommand); + +#define GFX_UI_MAIN(APPLICATION_ENTRY) \ + int __stdcall WinMain( \ + void* instance, \ + void* /* prevInstance */, \ + void* /* commandLine */, \ + int showCommand) { \ + return gfx::runWindowsApplication(&(APPLICATION_ENTRY), instance, showCommand); \ + } + +#else + +#define GFX_UI_MAIN(APPLICATION_ENTRY) GFX_CONSOLE_MAIN(APPLICATION_ENTRY) + +#endif + +} // gfx diff --git a/tools/graphics-app-framework/windows/win-window.cpp b/tools/graphics-app-framework/windows/win-window.cpp new file mode 100644 index 000000000..45315a27a --- /dev/null +++ b/tools/graphics-app-framework/windows/win-window.cpp @@ -0,0 +1,398 @@ +// win-window.cpp +#include "../window.h" + +#include <stdio.h> + +#ifdef _MSC_VER +#include <stddef.h> +#if (_MSC_VER < 1900) +#define snprintf sprintf_s +#endif +#endif + +#include <stdint.h> + +#if _WIN32 +#include <Windows.h> +#include <Windowsx.h> +#else +#error "The slang-graphics library currently only supports Windows platforms" +#endif + +namespace gfx { + +#if _WIN32 + +struct OSString +{ + OSString(char const* begin, char const* end) + { + _initialize(begin, end - begin); + } + + OSString(char const* begin) + { + _initialize(begin, strlen(begin)); + } + + ~OSString() + { + free(mBegin); + } + + operator WCHAR const*() + { + return mBegin; + } + +private: + WCHAR* mBegin; + WCHAR* mEnd; + + void _initialize(char const* input, size_t inputSize) + { + const DWORD dwFlags = 0; + int outputCodeUnitCount = ::MultiByteToWideChar(CP_UTF8, dwFlags, input, int(inputSize), nullptr, 0); + + WCHAR* buffer = (WCHAR*)malloc(sizeof(WCHAR) * (outputCodeUnitCount + 1)); + + ::MultiByteToWideChar(CP_UTF8, dwFlags, input, int(inputSize), buffer, outputCodeUnitCount); + buffer[outputCodeUnitCount] = 0; + + mBegin = buffer; + mEnd = buffer + outputCodeUnitCount; + } +}; + +struct ApplicationContext +{ + HINSTANCE instance; + int showCommand = SW_SHOWDEFAULT; + int resultCode = 0; +}; + +static uint64_t gTimerFrequency; + + +static void initApplication(ApplicationContext* context) +{ + LARGE_INTEGER timerFrequency; + QueryPerformanceFrequency(&timerFrequency); + gTimerFrequency = timerFrequency.QuadPart; +} + +/// Run an application given the specified callback and command-line arguments. +int runApplication( + ApplicationFunc func, + int argc, + char const* const* argv) +{ + ApplicationContext context; + context.instance = (HINSTANCE) GetModuleHandle(0); + initApplication(&context); + func(&context); + return context.resultCode; +} + +int runWindowsApplication( + ApplicationFunc func, + void* instance, + int showCommand) +{ + ApplicationContext context; + context.instance = (HINSTANCE) instance; + context.showCommand = showCommand; + initApplication(&context); + func(&context); + return context.resultCode; +} + +struct Window +{ + HWND handle; + WNDPROC nativeHook; + EventHandler eventHandler; + void* userData; +}; + +void setNativeWindowHook(Window* window, WNDPROC proc) +{ + window->nativeHook = proc; +} + +static KeyCode translateKeyCode(int vkey) +{ + switch( vkey ) + { + default: + return KeyCode::Unknown; + +#define CASE(FROM, TO) case FROM: return KeyCode::TO; + CASE('A', A); CASE('B', B); CASE('C', C); CASE('D', D); CASE('E', E); + CASE('F', F); CASE('G', G); CASE('H', H); CASE('I', I); CASE('J', J); + CASE('K', K); CASE('L', M); CASE('M', M); CASE('N', N); CASE('O', O); + CASE('P', P); CASE('Q', Q); CASE('R', R); CASE('S', S); CASE('T', T); + CASE('U', U); CASE('V', V); CASE('W', W); CASE('X', X); CASE('Y', Y); + CASE('Z', Z); +#undef CASE + +#define CASE(FROM, TO) case VK_##FROM: return KeyCode::TO; + CASE(SPACE, Space); +#undef CASE + } +} + +static LRESULT CALLBACK windowProc( + HWND windowHandle, + UINT message, + WPARAM wParam, + LPARAM lParam) +{ + Window* window = (Window*) GetWindowLongPtrW(windowHandle, GWLP_USERDATA); + + // Give the installed filter a chance to intercept messages. + // (This is used for ImGui) + if( window ) + { + if(auto nativeHook = window->nativeHook) + { + auto result = nativeHook(windowHandle, message, wParam, lParam); + if(result) + return result; + } + } + + auto eventHandler = window ? window->eventHandler : nullptr; + + switch (message) + { + case WM_CREATE: + { + auto createInfo = (CREATESTRUCTW*) lParam; + window = (Window*) createInfo->lpCreateParams; + window->handle = windowHandle; + + SetWindowLongPtrW(windowHandle, GWLP_USERDATA, (LONG_PTR) window); + } + break; + + case WM_KEYDOWN: + case WM_KEYUP: + { + int virtualKey = (int) wParam; + auto keyCode = translateKeyCode(virtualKey); + if(eventHandler) + { + Event event; + event.window = window; + event.code = message == WM_KEYDOWN ? EventCode::KeyDown : EventCode::KeyUp; + event.u.key = keyCode; + eventHandler(event); + } + } + break; + + case WM_LBUTTONDOWN: + case WM_LBUTTONUP: + { + if(eventHandler) + { + Event event; + event.window = window; + event.code = message == WM_LBUTTONDOWN ? EventCode::MouseDown : EventCode::MouseUp; + event.u.mouse.x = (float) GET_X_LPARAM(lParam); + event.u.mouse.y = (float) GET_Y_LPARAM(lParam); + eventHandler(event); + } + } + break; + + case WM_MOUSEMOVE: + { + if(eventHandler) + { + Event event; + event.window = window; + event.code = EventCode::MouseMoved; + event.u.mouse.x = (float) GET_X_LPARAM(lParam); + event.u.mouse.y = (float) GET_Y_LPARAM(lParam); + eventHandler(event); + } + } + break; + + case WM_CLOSE: + PostQuitMessage(0); + return 0; + } + + + return DefWindowProcW(windowHandle, message, wParam, lParam); +} + + +static ATOM createWindowClassAtom() +{ + WNDCLASSEXW windowClassDesc; + windowClassDesc.cbSize = sizeof(windowClassDesc); + windowClassDesc.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW; + windowClassDesc.lpfnWndProc = &windowProc; + windowClassDesc.cbClsExtra = 0; + windowClassDesc.cbWndExtra = 0; + windowClassDesc.hInstance = (HINSTANCE) GetModuleHandle(0); + windowClassDesc.hIcon = 0; + windowClassDesc.hCursor = 0; + windowClassDesc.hbrBackground = 0; + windowClassDesc.lpszMenuName = 0; + windowClassDesc.lpszClassName = L"SlangGraphicsWindow"; + windowClassDesc.hIconSm = 0; + ATOM windowClassAtom = RegisterClassExW(&windowClassDesc); + return windowClassAtom; +} + +static ATOM getWindowClassAtom() +{ + static ATOM windowClassAtom = createWindowClassAtom(); + return windowClassAtom; +} + +Window* createWindow(WindowDesc const& desc) +{ + Window* window = new Window(); + window->handle = nullptr; + window->nativeHook = nullptr; + window->eventHandler = desc.eventHandler; + window->userData = desc.userData; + + OSString windowTitle(desc.title); + + DWORD windowExtendedStyle = 0; + DWORD windowStyle = 0; + + HINSTANCE instance = (HINSTANCE) GetModuleHandle(0); + + HWND windowHandle = CreateWindowExW( + windowExtendedStyle, + (LPWSTR) getWindowClassAtom(), + windowTitle, + windowStyle, + 0, 0, // x, y + desc.width, desc.height, + NULL, // parent + NULL, // menu + instance, + window); + + if(!windowHandle) + { + delete window; + return nullptr; + } + + window->handle = windowHandle; + return window; +} + +void showWindow(Window* window) +{ + ShowWindow(window->handle, SW_SHOW); +} + +void* getPlatformWindowHandle(Window* window) +{ + return window->handle; +} + +void* getUserData(Window* window) +{ + return window->userData; +} + +bool dispatchEvents(ApplicationContext* context) +{ + for(;;) + { + MSG message; + + int result = PeekMessageW(&message, NULL, 0, 0, PM_REMOVE); + if (result != 0) + { + if (message.message == WM_QUIT) + { + context->resultCode = (int)message.wParam; + return false; + } + + TranslateMessage(&message); + DispatchMessageW(&message); + } + else + { + return true; + } + } + +} + +void exitApplication(ApplicationContext* context, int resultCode) +{ + ExitProcess(resultCode); +} + +void log(char const* message, ...) +{ + va_list args; + va_start(args, message); + + static const int kBufferSize = 1024; + char messageBuffer[kBufferSize]; + vsnprintf(messageBuffer, kBufferSize - 1, message, args); + messageBuffer[kBufferSize - 1] = 0; + + va_end(args); + + fputs(messageBuffer, stderr); + + OSString wideMessageBuffer(messageBuffer); + OutputDebugStringW(wideMessageBuffer); +} + +int reportError(char const* message, ...) +{ + va_list args; + va_start(args, message); + + static const int kBufferSize = 1024; + char messageBuffer[kBufferSize]; + vsnprintf(messageBuffer, kBufferSize - 1, message, args); + messageBuffer[kBufferSize - 1] = 0; + + va_end(args); + + fputs(messageBuffer, stderr); + + OSString wideMessageBuffer(messageBuffer); + OutputDebugStringW(wideMessageBuffer); + + return 1; +} + +uint64_t getCurrentTime() +{ + LARGE_INTEGER counter; + QueryPerformanceCounter(&counter); + return counter.QuadPart; +} + +uint64_t getTimerFrequency() +{ + return gTimerFrequency; +} + +#else + +// TODO: put an SDL version here + +#endif + +} // gfx |
