diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..3fea2770a1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +build +build_mac +build_ios +build_test \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index c494f7d281..8aaca4c89e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,6 +25,8 @@ option(IGL_WITH_OPENXR "Enable OpenXR" OFF) option(IGL_ENFORCE_LOGS "Enable logs in Release builds" ON) option(IGL_DEPLOY_DEPS "Deploy dependencies via CMake" ON) +option(IGLU_WITH_NANOVG "Enable IGLU Nanovg" OFF) + # cmake-format: on if(DEFINED ENV{VULKAN_SDK}) @@ -97,6 +99,7 @@ message(STATUS "IGL_WITH_OPENXR = ${IGL_WITH_OPENXR}") message(STATUS "IGL_ENFORCE_LOGS = ${IGL_ENFORCE_LOGS}") message(STATUS "IGL_DEPLOY_DEPS = ${IGL_DEPLOY_DEPS}") +message(STATUS "IGLU_WITH_NANOVG = ${IGLU_WITH_NANOVG}") # cmake-format: on if(APPLE) diff --git a/IGLU/CMakeLists.txt b/IGLU/CMakeLists.txt index b4d78c8606..618226b3f5 100644 --- a/IGLU/CMakeLists.txt +++ b/IGLU/CMakeLists.txt @@ -25,6 +25,9 @@ macro(ADD_IGLU_MODULE module) target_include_directories(IGLU${module} PUBLIC "${CMAKE_CURRENT_BINARY_DIR}/include") target_include_directories(IGLU${module} PUBLIC "${CMAKE_CURRENT_BINARY_DIR}/include_${module}") target_include_directories(IGLU${module} PUBLIC "${IGL_ROOT_DIR}") + if (IGLU_WITH_NANOVG) + target_include_directories(IGLU${module} PUBLIC "${IGL_ROOT_DIR}/third-party/deps/src/nanovg/src") + endif() endmacro() add_iglu_module(imgui) @@ -36,6 +39,9 @@ add_iglu_module(shaderCross) add_iglu_module(texture_accessor) add_iglu_module(texture_loader) add_iglu_module(uniform) +if (IGLU_WITH_NANOVG) + add_iglu_module(nanovg) +endif() # header-only add_library(IGLUsimdtypes INTERFACE) @@ -59,6 +65,11 @@ target_sources(IGLUimgui PRIVATE "${IGL_ROOT_DIR}/third-party/deps/src/imgui/img target_include_directories(IGLUimgui PUBLIC "${IGL_ROOT_DIR}/third-party/deps/src/imgui") target_include_directories(IGLUtexture_accessor PUBLIC "${IGL_ROOT_DIR}/third-party/deps/src/glew/include") +# Nanovg +if (IGLU_WITH_NANOVG) + target_sources(IGLUnanovg PRIVATE "${IGL_ROOT_DIR}/third-party/deps/src/nanovg/src/nanovg.c") +endif() + if(UNIX) if (CMAKE_C_COMPILER_ID STREQUAL "GNU") target_compile_options(IGLUimgui PUBLIC "-Wno-volatile") diff --git a/IGLU/nanovg/nanovg_igl.cpp b/IGLU/nanovg/nanovg_igl.cpp new file mode 100644 index 0000000000..3fa5f550f8 --- /dev/null +++ b/IGLU/nanovg/nanovg_igl.cpp @@ -0,0 +1,1498 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/* + * Base on https://github.com/ollix/MetalNanoVG + */ + +#include "nanovg_igl.h" +#include "nanovg.h" +#include "shader_metal.h" +#include "shader_opengl.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#define kVertexInputIndex 0 +#define kVertexUniformBlockIndex 1 +#define kFragmentUniformBlockIndex 2 + +namespace iglu::nanovg { + +struct igl_vector_uint2 { + uint32_t x; + uint32_t y; +}; + +enum ShaderType { + MNVG_SHADER_FILLGRAD, + MNVG_SHADER_FILLIMG, + MNVG_SHADER_IMG, +}; + +enum CallType { + MNVG_NONE = 0, + MNVG_FILL, + MNVG_CONVEXFILL, + MNVG_STROKE, + MNVG_TRIANGLES, +}; + +struct Blend { + igl::BlendFactor srcRGB; + igl::BlendFactor dstRGB; + igl::BlendFactor srcAlpha; + igl::BlendFactor dstAlpha; +}; + +struct UniformBufferIndex { + igl::IBuffer* buffer = nullptr; + void* data = nullptr; + size_t offset = 0; +}; + +struct Call { + int type; + int image; + int pathOffset; + int pathCount; + int triangleOffset; + int triangleCount; + int indexOffset; + int indexCount; + int strokeOffset; + int strokeCount; + UniformBufferIndex uboIndex; + UniformBufferIndex uboIndex2; + Blend blendFunc; +}; + +struct VertexUniforms { + iglu::simdtypes::float4x4 matrix; + iglu::simdtypes::float2 viewSize; +}; + +struct FragmentUniforms { + iglu::simdtypes::float3x3 scissorMat; + iglu::simdtypes::float3x3 paintMat; + iglu::simdtypes::float4 innerCol; + iglu::simdtypes::float4 outerCol; + iglu::simdtypes::float2 scissorExt; + iglu::simdtypes::float2 scissorScale; + iglu::simdtypes::float2 extent; + float radius; + float feather; + float strokeMult; + float strokeThr; + int texType; + ShaderType type; +}; + +struct Texture { + int Id; + int type; + int flags; + std::shared_ptr tex; + std::shared_ptr sampler; +}; + +class UniformBufferBlock { + public: + UniformBufferBlock(igl::IDevice* device, size_t blockSize) : blockSize_(blockSize) { + data_.resize(blockSize); + igl::BufferDesc desc(igl::BufferDesc::BufferTypeBits::Uniform, + data_.data(), + blockSize, + igl::ResourceStorage::Shared); + desc.hint = igl::BufferDesc::BufferAPIHintBits::UniformBlock; + desc.debugName = "fragment_uniform_buffer"; + buffer_ = device->createBuffer(desc, NULL); + } + + ~UniformBufferBlock() { + IGL_LOG_DEBUG("iglu::nanovg::UniformBufferBlock::~UniformBufferBlock()\n"); + } + + bool checkLeftSpace(size_t dataSize) { + return current_ + dataSize <= blockSize_; + } + + UniformBufferIndex allocData(size_t dataSize) { + assert(checkLeftSpace(dataSize)); + + UniformBufferIndex index{buffer_.get(), data_.data() + current_, current_}; + + current_ += dataSize; + + return index; + } + + void uploadToGpu() { + buffer_->upload(data_.data(), igl::BufferRange(data_.size())); + } + + void reset() { + current_ = 0; + } + + private: + std::shared_ptr buffer_; + std::vector data_; + size_t blockSize_ = 0; + size_t current_ = 0; +}; + +class UniformBufferPool { + public: + UniformBufferPool(igl::IDevice* device, size_t blockSize) : + device_(device), blockSize_(blockSize) { + allocNewBlock(); + } + + UniformBufferIndex allocData(size_t dataSize) { + if (!bufferBlocks_[currentBlockIndex]->checkLeftSpace(dataSize)) { + currentBlockIndex++; + if (bufferBlocks_.size() <= currentBlockIndex) { + allocNewBlock(); + } + } + + return bufferBlocks_[currentBlockIndex]->allocData(dataSize); + } + + void uploadToGpu() { + for (auto& block : bufferBlocks_) { + block->uploadToGpu(); + } + } + + void reset() { + currentBlockIndex = 0; + for (auto& block : bufferBlocks_) { + block->reset(); + } + } + + private: + void allocNewBlock() { + bufferBlocks_.emplace_back(std::make_shared(device_, blockSize_)); + } + + private: + std::vector> bufferBlocks_; + size_t blockSize_ = 0; + igl::IDevice* device_ = nullptr; + size_t currentBlockIndex = 0; +}; + +struct Buffers { + std::shared_ptr commandBuffer; + bool isBusy = false; + int image = 0; + std::shared_ptr vertexUniformBuffer; + VertexUniforms vertexUniforms; + std::shared_ptr stencilTexture; + std::vector calls; + int ccalls = 0; + int ncalls = 0; + std::shared_ptr indexBuffer; + std::vector indexes; + int cindexes = 0; + int nindexes = 0; + std::shared_ptr vertBuffer; + std::vector verts; + int cverts = 0; + int nverts = 0; + std::shared_ptr uniformBufferPool; + + Buffers(igl::IDevice* device, size_t uniformBufferBlockSize) { + vertexUniforms.matrix = iglu::simdtypes::float4x4(1.0f); + uniformBufferPool = std::make_shared(device, uniformBufferBlockSize); + } + + ~Buffers() { + IGL_LOG_DEBUG("iglu::nanovg::Buffers::~Buffers()\n"); + } + + void uploadToGpu() { + if (vertBuffer) { + vertBuffer->upload(verts.data(), igl::BufferRange(verts.size() * sizeof(NVGvertex))); + } + + if (indexBuffer) { + indexBuffer->upload(indexes.data(), igl::BufferRange(indexes.size() * sizeof(uint32_t))); + } + + if (vertexUniformBuffer) { + vertexUniformBuffer->upload(&vertexUniforms, igl::BufferRange(sizeof(VertexUniforms))); + } + + uniformBufferPool->uploadToGpu(); + } +}; + +static bool convertBlendFuncFactor(int factor, igl::BlendFactor* result) { + if (factor == NVG_ZERO) + *result = igl::BlendFactor::Zero; + else if (factor == NVG_ONE) + *result = igl::BlendFactor::One; + else if (factor == NVG_SRC_COLOR) + *result = igl::BlendFactor::SrcColor; + else if (factor == NVG_ONE_MINUS_SRC_COLOR) + *result = igl::BlendFactor::OneMinusSrcColor; + else if (factor == NVG_DST_COLOR) + *result = igl::BlendFactor::DstColor; + else if (factor == NVG_ONE_MINUS_DST_COLOR) + *result = igl::BlendFactor::OneMinusDstColor; + else if (factor == NVG_SRC_ALPHA) + *result = igl::BlendFactor::SrcAlpha; + else if (factor == NVG_ONE_MINUS_SRC_ALPHA) + *result = igl::BlendFactor::OneMinusSrcAlpha; + else if (factor == NVG_DST_ALPHA) + *result = igl::BlendFactor::DstAlpha; + else if (factor == NVG_ONE_MINUS_DST_ALPHA) + *result = igl::BlendFactor::OneMinusDstAlpha; + else if (factor == NVG_SRC_ALPHA_SATURATE) + *result = igl::BlendFactor::SrcAlphaSaturated; + else + return false; + return true; +} + +static int MAXINT(int a, int b) { + return a > b ? a : b; +} + +static int maxVertexCount(const NVGpath* paths, int npaths, int* indexCount, int* strokeCount) { + int count = 0; + if (indexCount != NULL) + *indexCount = 0; + if (strokeCount != NULL) + *strokeCount = 0; + NVGpath* path = (NVGpath*)&paths[0]; + for (int i = npaths; i--; ++path) { + const int nfill = path->nfill; + if (nfill > 2) { + count += nfill; + if (indexCount != NULL) + *indexCount += (nfill - 2) * 3; + } + if (path->nstroke > 0) { + const int nstroke = path->nstroke + 2; + count += nstroke; + if (strokeCount != NULL) + *strokeCount += nstroke; + } + } + return count; +} + +static iglu::simdtypes::float4 preMultiplyColor(NVGcolor c) { + c.r *= c.a; + c.g *= c.a; + c.b *= c.a; + iglu::simdtypes::float4 ret; + memcpy(&ret, &c, sizeof(float) * 4); + return ret; +} + +static void transformToMat3x3(iglu::simdtypes::float3x3* m3, float* t) { + float columns_0[3] = {t[0], t[1], 0.0f}; + float columns_1[3] = {t[2], t[3], 0.0f}; + float columns_2[3] = {t[4], t[5], 1.0f}; + + memcpy(&m3->columns[0], columns_0, 3 * sizeof(float)); + memcpy(&m3->columns[1], columns_1, 3 * sizeof(float)); + memcpy(&m3->columns[2], columns_2, 3 * sizeof(float)); +} + +static void setVertextData(NVGvertex* vtx, float x, float y, float u, float v) { + vtx->x = x; + vtx->y = y; + vtx->u = u; + vtx->v = v; +} + +class Context { + public: + igl::IDevice* device_ = nullptr; + igl::IRenderCommandEncoder* renderEncoder_ = nullptr; + + size_t fragmentUniformBufferSize_; + size_t maxUniformBufferSize_; + int indexSize_; + int flags_; + igl_vector_uint2 viewPortSize_; + + igl::IFramebuffer* framebuffer_ = nullptr; + + // Textures + std::vector> textures_; + int textureId_; + + // Per frame buffers + std::shared_ptr curBuffers_ = nullptr; + std::vector> allBuffers_; + int maxBuffers_; + + // Cached states. + Blend* blendFunc_ = nullptr; + std::shared_ptr defaultStencilState_; + std::shared_ptr fillShapeStencilState_; + std::shared_ptr fillAntiAliasStencilState_; + std::shared_ptr fillStencilState_; + std::shared_ptr strokeShapeStencilState_; + std::shared_ptr strokeAntiAliasStencilState_; + std::shared_ptr strokeClearStencilState_; + std::shared_ptr fragmentFunction_; + std::shared_ptr vertexFunction_; + igl::TextureFormat piplelinePixelFormat_; + std::shared_ptr pipelineState_; + std::shared_ptr pipelineStateTriangleStrip_; + std::shared_ptr stencilOnlyPipelineState_; + std::shared_ptr stencilOnlyPipelineStateTriangleStrip_; + std::shared_ptr pseudoSampler_; + std::shared_ptr pseudoTexture_; + igl::VertexInputStateDesc vertexDescriptor_; + + Context() { + IGL_LOG_DEBUG("iglu::nanovg::Context::Context()\n"); + } + + ~Context() { + IGL_LOG_DEBUG("iglu::nanovg::Context::~Context()\n"); + } + + Call* allocCall() { + Call* ret = NULL; + if (curBuffers_->ncalls + 1 > curBuffers_->ccalls) { + int ccalls = MAXINT(curBuffers_->ncalls + 1, 128) + curBuffers_->ccalls / 2; + curBuffers_->calls.resize(ccalls); + curBuffers_->ccalls = ccalls; + } + ret = &curBuffers_->calls[curBuffers_->ncalls++]; + memset(ret, 0, sizeof(Call)); + return ret; + } + + UniformBufferIndex allocFragUniforms(size_t dataSize) { + return curBuffers_->uniformBufferPool->allocData(dataSize); + } + + int allocIndexes(int n) { + int ret = 0; + if (curBuffers_->nindexes + n > curBuffers_->cindexes) { + int cindexes = MAXINT(curBuffers_->nindexes + n, 4096) + curBuffers_->cindexes / 2; + curBuffers_->indexes.resize(cindexes); + + igl::BufferDesc desc(igl::BufferDesc::BufferTypeBits::Index, + curBuffers_->indexes.data(), + indexSize_ * cindexes, + igl::ResourceStorage::Shared); + desc.debugName = "index_buffer"; + std::shared_ptr buffer = device_->createBuffer(desc, NULL); + + curBuffers_->indexBuffer = buffer; + curBuffers_->cindexes = cindexes; + } + ret = curBuffers_->nindexes; + curBuffers_->nindexes += n; + return ret; + } + + std::shared_ptr allocTexture() { + std::shared_ptr tex = nullptr; + + for (auto& texture : textures_) { + if (texture->Id == 0) { + tex = texture; + break; + } + } + if (tex == nullptr) { + tex = std::make_shared(); + textures_.emplace_back(tex); + } + tex->Id = ++textureId_; + return tex; + } + + int allocVerts(int n) { + int ret = 0; + if (curBuffers_->nverts + n > curBuffers_->cverts) { + int cverts = MAXINT(curBuffers_->nverts + n, 4096) + curBuffers_->cverts / 2; + curBuffers_->verts.resize(cverts); + + igl::BufferDesc desc(igl::BufferDesc::BufferTypeBits::Vertex, + curBuffers_->verts.data(), + sizeof(NVGvertex) * cverts, + igl::ResourceStorage::Shared); + desc.debugName = "vertex_buffer"; + std::shared_ptr buffer = device_->createBuffer(desc, NULL); + + curBuffers_->vertBuffer = buffer; + curBuffers_->cverts = cverts; + } + ret = curBuffers_->nverts; + curBuffers_->nverts += n; + return ret; + } + + Blend blendCompositeOperation(NVGcompositeOperationState op) { + Blend blend; + if (!convertBlendFuncFactor(op.srcRGB, &blend.srcRGB) || + !convertBlendFuncFactor(op.dstRGB, &blend.dstRGB) || + !convertBlendFuncFactor(op.srcAlpha, &blend.srcAlpha) || + !convertBlendFuncFactor(op.dstAlpha, &blend.dstAlpha)) { + blend.srcRGB = igl::BlendFactor::One; + blend.dstRGB = igl::BlendFactor::OneMinusSrcAlpha; + blend.srcAlpha = igl::BlendFactor::One; + blend.dstAlpha = igl::BlendFactor::OneMinusSrcAlpha; + } + return blend; + } + + int convertPaintForFrag(FragmentUniforms* frag, + NVGpaint* paint, + NVGscissor* scissor, + float width, + float fringe, + float strokeThr) { + std::shared_ptr tex = nullptr; + float invxform[6]; + + memset(frag, 0, sizeof(*frag)); + + frag->innerCol = preMultiplyColor(paint->innerColor); + frag->outerCol = preMultiplyColor(paint->outerColor); + + if (scissor->extent[0] < -0.5f || scissor->extent[1] < -0.5f) { + frag->scissorMat = iglu::simdtypes::float3x3(0); + frag->scissorExt[0] = 1.0f; + frag->scissorExt[1] = 1.0f; + frag->scissorScale[0] = 1.0f; + frag->scissorScale[1] = 1.0f; + } else { + nvgTransformInverse(invxform, scissor->xform); + transformToMat3x3(&frag->scissorMat, invxform); + frag->scissorExt[0] = scissor->extent[0]; + frag->scissorExt[1] = scissor->extent[1]; + frag->scissorScale[0] = + sqrtf(scissor->xform[0] * scissor->xform[0] + scissor->xform[2] * scissor->xform[2]) / + fringe; + frag->scissorScale[1] = + sqrtf(scissor->xform[1] * scissor->xform[1] + scissor->xform[3] * scissor->xform[3]) / + fringe; + } + + frag->extent = iglu::simdtypes::float2{paint->extent[0], paint->extent[1]}; + frag->strokeMult = (width * 0.5f + fringe * 0.5f) / fringe; + frag->strokeThr = strokeThr; + + if (paint->image != 0) { + tex = findTexture(paint->image); + if (tex == nullptr) + return 0; + if (tex->flags & NVG_IMAGE_FLIPY) { + float m1[6], m2[6]; + nvgTransformTranslate(m1, 0.0f, frag->extent[0] * 0.5f); + nvgTransformMultiply(m1, paint->xform); + nvgTransformScale(m2, 1.0f, -1.0f); + nvgTransformMultiply(m2, m1); + nvgTransformTranslate(m1, 0.0f, -frag->extent[1] * 0.5f); + nvgTransformMultiply(m1, m2); + nvgTransformInverse(invxform, m1); + } else { + nvgTransformInverse(invxform, paint->xform); + } + frag->type = MNVG_SHADER_FILLIMG; + + if (tex->type == NVG_TEXTURE_RGBA) + frag->texType = (tex->flags & NVG_IMAGE_PREMULTIPLIED) ? 0 : 1; + else + frag->texType = 2; + } else { + frag->type = MNVG_SHADER_FILLGRAD; + frag->radius = paint->radius; + frag->feather = paint->feather; + nvgTransformInverse(invxform, paint->xform); + } + + transformToMat3x3(&frag->paintMat, invxform); + + return 1; + } + + void bindRenderPipeline(const std::shared_ptr& pipelineState, + const UniformBufferIndex* uboIndex = nullptr) { + renderEncoder_->bindRenderPipelineState(pipelineState); + renderEncoder_->bindVertexBuffer(kVertexInputIndex, *curBuffers_->vertBuffer, 0); + renderEncoder_->bindBuffer(kVertexUniformBlockIndex, curBuffers_->vertexUniformBuffer.get(), 0); + if (uboIndex) { + renderEncoder_->bindBuffer(kFragmentUniformBlockIndex, uboIndex->buffer, uboIndex->offset); + } + } + + void convexFill(Call* call) { + const int kIndexBufferOffset = call->indexOffset * indexSize_; + bindRenderPipeline(pipelineState_); + setUniforms(call->uboIndex, call->image); + if (call->indexCount > 0) { + renderEncoder_->bindIndexBuffer( + *curBuffers_->indexBuffer, igl::IndexFormat::UInt32, kIndexBufferOffset); + renderEncoder_->drawIndexed(call->indexCount); + } + + // Draw fringes + if (call->strokeCount > 0) { + bindRenderPipeline(pipelineStateTriangleStrip_); + renderEncoder_->draw(call->strokeCount, 1, call->strokeOffset); + } + } + + void fill(Call* call) { + // Draws shapes. + const int kIndexBufferOffset = call->indexOffset * indexSize_; + bindRenderPipeline(stencilOnlyPipelineState_, &call->uboIndex); + renderEncoder_->bindDepthStencilState(fillShapeStencilState_); + if (call->indexCount > 0) { + renderEncoder_->bindIndexBuffer( + *curBuffers_->indexBuffer, igl::IndexFormat::UInt32, kIndexBufferOffset); + renderEncoder_->drawIndexed(call->indexCount); + } + + // Restores states. + bindRenderPipeline(pipelineStateTriangleStrip_); + + // Draws anti-aliased fragments. + setUniforms(call->uboIndex, call->image); + if (flags_ & NVG_ANTIALIAS && call->strokeCount > 0) { + renderEncoder_->bindDepthStencilState(fillAntiAliasStencilState_); + renderEncoder_->draw(call->strokeCount, 1, call->strokeOffset); + } + + // Draws fill. + renderEncoder_->bindDepthStencilState(fillStencilState_); + renderEncoder_->draw(call->triangleCount, 1, call->triangleOffset); + renderEncoder_->bindDepthStencilState(defaultStencilState_); + } + + std::shared_ptr findTexture(int _id) { + for (auto& texture : textures_) { + if (texture->Id == _id) + return texture; + } + return nullptr; + } + + void renderCancel() { + curBuffers_->image = 0; + curBuffers_->isBusy = false; + curBuffers_->nindexes = 0; + curBuffers_->nverts = 0; + curBuffers_->ncalls = 0; + curBuffers_->uniformBufferPool->reset(); + } + + void renderCommandEncoderWithColorTexture() { + renderEncoder_->setStencilReferenceValue(0); + renderEncoder_->bindViewport( + {0.0, 0.0, (float)viewPortSize_.x, (float)viewPortSize_.y, 0.0, 1.0}); + renderEncoder_->bindVertexBuffer(kVertexInputIndex, *curBuffers_->vertBuffer, 0); + renderEncoder_->bindBuffer(kVertexUniformBlockIndex, curBuffers_->vertexUniformBuffer.get(), 0); + } + + int renderCreate() { + bool creates_pseudo_texture = false; + + igl::Result result; + + if (device_->getBackendType() == igl::BackendType::Metal) { + const std::string vertexEntryPoint = "vertexShader"; + std::string fragmentEntryPoint = (flags_ & NVG_ANTIALIAS) ? "fragmentShaderAA" + : "fragmentShader"; + + std::unique_ptr shader_library = + igl::ShaderLibraryCreator::fromStringInput( + *device_, metalShader.c_str(), vertexEntryPoint, fragmentEntryPoint, "", &result); + + vertexFunction_ = shader_library->getShaderModule(vertexEntryPoint); + fragmentFunction_ = shader_library->getShaderModule(fragmentEntryPoint); + } else if (device_->getBackendType() == igl::BackendType::OpenGL) { +#if IGL_PLATFORM_ANDROID || IGL_PLATFORM_IOS || IGL_PLATFORM_LINUX + auto codeVS = std::regex_replace( + openglVertexShaderHeader410, std::regex("#version 410"), "#version 300 es"); + auto codeFS = std::regex_replace( + openglFragmentShaderHeader410, std::regex("#version 410"), "#version 300 es"); + + codeVS += openglVertexShaderBody; + codeFS += ((flags_ & NVG_ANTIALIAS) ? openglAntiAliasingFragmentShaderBody + : openglNoAntiAliasingFragmentShaderBody); +#else + auto codeVS = openglVertexShaderHeader410 + openglVertexShaderBody; + auto codeFS = openglFragmentShaderHeader410 + ((flags_ & NVG_ANTIALIAS) + ? openglAntiAliasingFragmentShaderBody + : openglNoAntiAliasingFragmentShaderBody); + +#endif + + std::unique_ptr shader_stages = + igl::ShaderStagesCreator::fromModuleStringInput( + *device_, codeVS.c_str(), "main", "", codeFS.c_str(), "main", "", nullptr); + + vertexFunction_ = shader_stages->getVertexModule(); + fragmentFunction_ = shader_stages->getFragmentModule(); + } else if (device_->getBackendType() == igl::BackendType::Vulkan) { + auto codeVS = openglVertexShaderHeader460 + openglVertexShaderBody; + auto codeFS = openglFragmentShaderHeader460 + ((flags_ & NVG_ANTIALIAS) + ? openglAntiAliasingFragmentShaderBody + : openglNoAntiAliasingFragmentShaderBody); + + std::unique_ptr shader_stages = + igl::ShaderStagesCreator::fromModuleStringInput( + *device_, codeVS.c_str(), "main", "", codeFS.c_str(), "main", "", nullptr); + + vertexFunction_ = shader_stages->getVertexModule(); + fragmentFunction_ = shader_stages->getFragmentModule(); + } + + maxBuffers_ = 3; + + for (int i = maxBuffers_; i--;) { + allBuffers_.emplace_back(std::make_shared(device_, maxUniformBufferSize_)); + } + + // Initializes vertex descriptor. + vertexDescriptor_.numAttributes = 2; + vertexDescriptor_.attributes[0].format = igl::VertexAttributeFormat::Float2; + vertexDescriptor_.attributes[0].name = "pos"; + vertexDescriptor_.attributes[0].bufferIndex = 0; + vertexDescriptor_.attributes[0].offset = offsetof(NVGvertex, x); + vertexDescriptor_.attributes[0].location = 0; + + vertexDescriptor_.attributes[1].format = igl::VertexAttributeFormat::Float2; + vertexDescriptor_.attributes[1].name = "tcoord"; + vertexDescriptor_.attributes[1].bufferIndex = 0; + vertexDescriptor_.attributes[1].offset = offsetof(NVGvertex, u); + vertexDescriptor_.attributes[1].location = 1; + + vertexDescriptor_.numInputBindings = 1; + vertexDescriptor_.inputBindings[0].stride = sizeof(NVGvertex); + vertexDescriptor_.inputBindings[0].sampleFunction = igl::VertexSampleFunction::PerVertex; + + // Initialzes textures. + textureId_ = 0; + + // Initializes default sampler descriptor. + igl::SamplerStateDesc samplerDescriptor; + samplerDescriptor.debugName = "pseudoSampler"; + pseudoSampler_ = device_->createSamplerState(samplerDescriptor, &result); + + // Initializes pseudo texture for macOS. + if (creates_pseudo_texture) { + const int kPseudoTextureImage = renderCreateTextureWithType(NVG_TEXTURE_ALPHA, 1, 1, 0, NULL); + std::shared_ptr tex = findTexture(kPseudoTextureImage); + pseudoTexture_ = tex->tex; + } + + // Initializes default blend states. + blendFunc_ = (Blend*)malloc(sizeof(Blend)); + blendFunc_->srcRGB = igl::BlendFactor::One; + blendFunc_->dstRGB = igl::BlendFactor::OneMinusSrcAlpha; + blendFunc_->srcAlpha = igl::BlendFactor::One; + blendFunc_->dstAlpha = igl::BlendFactor::OneMinusSrcAlpha; + + // Initializes stencil states. + igl::DepthStencilStateDesc stencilDescriptor; + + // Default stencil state. + stencilDescriptor.debugName = "defaultStencilState"; + defaultStencilState_ = device_->createDepthStencilState(stencilDescriptor, &result); + + // Fill shape stencil. + igl::StencilStateDesc frontFaceStencilDescriptor; + frontFaceStencilDescriptor.stencilCompareFunction = igl::CompareFunction::AlwaysPass; + frontFaceStencilDescriptor.depthStencilPassOperation = igl::StencilOperation::IncrementWrap; + + igl::StencilStateDesc backFaceStencilDescriptor; + backFaceStencilDescriptor.stencilCompareFunction = igl::CompareFunction::AlwaysPass; + backFaceStencilDescriptor.depthStencilPassOperation = igl::StencilOperation::DecrementWrap; + + stencilDescriptor.compareFunction = igl::CompareFunction::AlwaysPass; + stencilDescriptor.backFaceStencil = backFaceStencilDescriptor; + stencilDescriptor.frontFaceStencil = frontFaceStencilDescriptor; + stencilDescriptor.debugName = "fillShapeStencilState"; + fillShapeStencilState_ = device_->createDepthStencilState(stencilDescriptor, &result); + + // Fill anti-aliased stencil. + frontFaceStencilDescriptor.stencilCompareFunction = igl::CompareFunction::Equal; + frontFaceStencilDescriptor.stencilFailureOperation = igl::StencilOperation::Keep; + frontFaceStencilDescriptor.depthFailureOperation = igl::StencilOperation::Keep; + frontFaceStencilDescriptor.depthStencilPassOperation = igl::StencilOperation::Zero; + + stencilDescriptor.backFaceStencil = igl::StencilStateDesc(); + stencilDescriptor.frontFaceStencil = frontFaceStencilDescriptor; + stencilDescriptor.debugName = "fillAntiAliasStencilState"; + fillAntiAliasStencilState_ = device_->createDepthStencilState(stencilDescriptor, &result); + + // Fill stencil. + frontFaceStencilDescriptor.stencilCompareFunction = igl::CompareFunction::NotEqual; + frontFaceStencilDescriptor.stencilFailureOperation = igl::StencilOperation::Zero; + frontFaceStencilDescriptor.depthFailureOperation = igl::StencilOperation::Zero; + frontFaceStencilDescriptor.depthStencilPassOperation = igl::StencilOperation::Zero; + + stencilDescriptor.backFaceStencil = igl::StencilStateDesc(); + stencilDescriptor.frontFaceStencil = frontFaceStencilDescriptor; + stencilDescriptor.debugName = "fillStencilState"; + fillStencilState_ = device_->createDepthStencilState(stencilDescriptor, &result); + + // Stroke shape stencil. + frontFaceStencilDescriptor.stencilCompareFunction = igl::CompareFunction::Equal; + frontFaceStencilDescriptor.stencilFailureOperation = igl::StencilOperation::Keep; + frontFaceStencilDescriptor.depthFailureOperation = igl::StencilOperation::Keep; + frontFaceStencilDescriptor.depthStencilPassOperation = igl::StencilOperation::IncrementClamp; + + stencilDescriptor.backFaceStencil = igl::StencilStateDesc(); + stencilDescriptor.frontFaceStencil = frontFaceStencilDescriptor; + stencilDescriptor.debugName = "strokeShapeStencilState"; + strokeShapeStencilState_ = device_->createDepthStencilState(stencilDescriptor, &result); + + // Stroke anti-aliased stencil. + frontFaceStencilDescriptor.depthStencilPassOperation = igl::StencilOperation::Keep; + + stencilDescriptor.backFaceStencil = igl::StencilStateDesc(); + stencilDescriptor.frontFaceStencil = frontFaceStencilDescriptor; + stencilDescriptor.debugName = "strokeAntiAliasStencilState"; + strokeAntiAliasStencilState_ = device_->createDepthStencilState(stencilDescriptor, &result); + + // Stroke clear stencil. + frontFaceStencilDescriptor.stencilCompareFunction = igl::CompareFunction::AlwaysPass; + frontFaceStencilDescriptor.stencilFailureOperation = igl::StencilOperation::Zero; + frontFaceStencilDescriptor.depthFailureOperation = igl::StencilOperation::Zero; + frontFaceStencilDescriptor.depthStencilPassOperation = igl::StencilOperation::Zero; + + stencilDescriptor.backFaceStencil = igl::StencilStateDesc(); + stencilDescriptor.frontFaceStencil = frontFaceStencilDescriptor; + stencilDescriptor.debugName = "strokeClearStencilState"; + strokeClearStencilState_ = device_->createDepthStencilState(stencilDescriptor, &result); + return 1; + } + + int renderCreateTextureWithType(int type, + int width, + int height, + int imageFlags, + const unsigned char* data) { + std::shared_ptr tex = allocTexture(); + + if (tex == nullptr) + return 0; + + igl::TextureFormat pixelFormat = igl::TextureFormat::RGBA_UNorm8; + if (type == NVG_TEXTURE_ALPHA) { + pixelFormat = igl::TextureFormat::R_UNorm8; + } + + tex->type = type; + tex->flags = imageFlags; + + // todo: + //(imageFlags & NVG_IMAGE_GENERATE_MIPMAPS ? true : false) + + igl::TextureDesc textureDescriptor = igl::TextureDesc::new2D( + pixelFormat, width, height, igl::TextureDesc::TextureUsageBits::Sampled); + + tex->tex = device_->createTexture(textureDescriptor, NULL); + + if (data != NULL) { + int bytesPerRow = 0; + if (tex->type == NVG_TEXTURE_RGBA) { + bytesPerRow = width * 4; + } else { + bytesPerRow = width; + } + + tex->tex->upload(igl::TextureRangeDesc::new2D(0, 0, width, height), data); + } + + igl::SamplerStateDesc samplerDescriptor; + if (imageFlags & NVG_IMAGE_NEAREST) { + samplerDescriptor.minFilter = igl::SamplerMinMagFilter::Nearest; + samplerDescriptor.magFilter = igl::SamplerMinMagFilter::Nearest; + if (imageFlags & NVG_IMAGE_GENERATE_MIPMAPS) + samplerDescriptor.mipFilter = igl::SamplerMipFilter::Nearest; + } else { + samplerDescriptor.minFilter = igl::SamplerMinMagFilter::Linear; + samplerDescriptor.magFilter = igl::SamplerMinMagFilter::Linear; + if (imageFlags & NVG_IMAGE_GENERATE_MIPMAPS) + samplerDescriptor.mipFilter = igl::SamplerMipFilter::Linear; + } + + if (imageFlags & NVG_IMAGE_REPEATX) { + samplerDescriptor.addressModeU = igl::SamplerAddressMode::Repeat; + } else { + samplerDescriptor.addressModeU = igl::SamplerAddressMode::Clamp; + } + + if (imageFlags & NVG_IMAGE_REPEATY) { + samplerDescriptor.addressModeV = igl::SamplerAddressMode::Repeat; + } else { + samplerDescriptor.addressModeV = igl::SamplerAddressMode::Clamp; + } + + samplerDescriptor.debugName = "textureSampler"; + tex->sampler = device_->createSamplerState(samplerDescriptor, NULL); + + return tex->Id; + } + + void renderDelete() { + for (auto& buffers : allBuffers_) { + buffers->commandBuffer = nullptr; + buffers->vertexUniformBuffer = nullptr; + buffers->stencilTexture = nullptr; + buffers->indexBuffer = nullptr; + buffers->vertBuffer = nullptr; + buffers->uniformBufferPool = nullptr; + } + + for (auto& texture : textures_) { + texture->tex = nullptr; + texture->sampler = nullptr; + } + + free(blendFunc_); + renderEncoder_ = nullptr; + textures_.clear(); + allBuffers_.clear(); + defaultStencilState_ = nullptr; + fillShapeStencilState_ = nullptr; + fillAntiAliasStencilState_ = nullptr; + strokeShapeStencilState_ = nullptr; + strokeAntiAliasStencilState_ = nullptr; + strokeClearStencilState_ = nullptr; + pipelineState_ = nullptr; + stencilOnlyPipelineState_ = nullptr; + pseudoSampler_ = nullptr; + pseudoTexture_ = nullptr; + device_ = nullptr; + } + + int renderDeleteTexture(int image) { + for (auto& texture : textures_) { + if (texture->Id == image) { + if (texture->tex != nullptr && (texture->flags & NVG_IMAGE_NODELETE) == 0) { + texture->tex = nullptr; + texture->sampler = nullptr; + } + texture->Id = 0; + texture->flags = 0; + return 1; + } + } + return 0; + } + + void renderFillWithPaint(NVGpaint* paint, + NVGcompositeOperationState compositeOperation, + NVGscissor* scissor, + float fringe, + const float* bounds, + const NVGpath* paths, + int npaths) { + Call* call = allocCall(); + NVGvertex* quad = nullptr; + + if (call == NULL) + return; + + call->type = MNVG_FILL; + call->triangleCount = 4; + call->image = paint->image; + call->blendFunc = blendCompositeOperation(compositeOperation); + + if (npaths == 1 && paths[0].convex) { + call->type = MNVG_CONVEXFILL; + call->triangleCount = 0; // Bounding box fill quad not needed for convex fill + } + + // Allocate vertices for all the paths. + int indexCount, strokeCount = 0; + int maxverts = maxVertexCount(paths, npaths, &indexCount, &strokeCount) + call->triangleCount; + int vertOffset = allocVerts(maxverts); + if (vertOffset == -1) { + // We get here if call alloc was ok, but something else is not. + // Roll back the last call to prevent drawing it. + if (curBuffers_->ncalls > 0) + curBuffers_->ncalls--; + return; + } + + int indexOffset = allocIndexes(indexCount); + if (indexOffset == -1) { + // We get here if call alloc was ok, but something else is not. + // Roll back the last call to prevent drawing it. + if (curBuffers_->ncalls > 0) + curBuffers_->ncalls--; + return; + } + call->indexOffset = indexOffset; + call->indexCount = indexCount; + uint32_t* index = &curBuffers_->indexes[indexOffset]; + + int strokeVertOffset = vertOffset + (maxverts - strokeCount); + call->strokeOffset = strokeVertOffset + 1; + call->strokeCount = strokeCount - 2; + NVGvertex* strokeVert = curBuffers_->verts.data() + strokeVertOffset; + + NVGpath* path = (NVGpath*)&paths[0]; + for (int i = npaths; i--; ++path) { + if (path->nfill > 2) { + memcpy(&curBuffers_->verts[vertOffset], path->fill, sizeof(NVGvertex) * path->nfill); + + int hubVertOffset = vertOffset++; + for (int j = 2; j < path->nfill; j++) { + *index++ = hubVertOffset; + *index++ = vertOffset++; + *index++ = vertOffset; + } + vertOffset++; + } + if (path->nstroke > 0) { + memcpy(strokeVert, path->stroke, sizeof(NVGvertex)); + ++strokeVert; + memcpy(strokeVert, path->stroke, sizeof(NVGvertex) * path->nstroke); + strokeVert += path->nstroke; + memcpy(strokeVert, path->stroke + path->nstroke - 1, sizeof(NVGvertex)); + ++strokeVert; + } + } + + // Setup uniforms for draw calls + if (call->type == MNVG_FILL) { + // Quad + call->triangleOffset = vertOffset; + quad = &curBuffers_->verts[call->triangleOffset]; + setVertextData(&quad[0], bounds[2], bounds[3], 0.5f, 1.0f); + setVertextData(&quad[1], bounds[2], bounds[1], 0.5f, 1.0f); + setVertextData(&quad[2], bounds[0], bounds[3], 0.5f, 1.0f); + setVertextData(&quad[3], bounds[0], bounds[1], 0.5f, 1.0f); + } + + // Fill shader + call->uboIndex = allocFragUniforms(fragmentUniformBufferSize_); + convertPaintForFrag( + (FragmentUniforms*)call->uboIndex.data, paint, scissor, fringe, fringe, -1.0f); + } + + void renderFlush() { + // Cancelled if the drawable is invisible. + if (viewPortSize_.x == 0 || viewPortSize_.y == 0) { + renderCancel(); + return; + } + + curBuffers_->uploadToGpu(); + + renderCommandEncoderWithColorTexture(); + + Call* call = &curBuffers_->calls[0]; + for (int i = curBuffers_->ncalls; i--; ++call) { + Blend* blend = &call->blendFunc; + + updateRenderPipelineStatesForBlend(blend); + + if (call->type == MNVG_FILL) { + renderEncoder_->pushDebugGroupLabel("fill"); + fill(call); + } else if (call->type == MNVG_CONVEXFILL) { + renderEncoder_->pushDebugGroupLabel("convexFill"); + convexFill(call); + } else if (call->type == MNVG_STROKE) { + renderEncoder_->pushDebugGroupLabel("stroke"); + stroke(call); + } else if (call->type == MNVG_TRIANGLES) { + renderEncoder_->pushDebugGroupLabel("triangles"); + triangles(call); + } + + renderEncoder_->popDebugGroupLabel(); + } + + curBuffers_->isBusy = false; + curBuffers_->commandBuffer = nullptr; + curBuffers_->image = 0; + curBuffers_->nindexes = 0; + curBuffers_->nverts = 0; + curBuffers_->ncalls = 0; + curBuffers_->uniformBufferPool->reset(); + } + + int renderGetTextureSizeForImage(int image, int* width, int* height) { + std::shared_ptr tex = findTexture(image); + if (tex == nullptr) + return 0; + *width = (int)tex->tex->getSize().width; + *height = (int)tex->tex->getSize().height; + return 1; + } + + void renderStrokeWithPaint(NVGpaint* paint, + NVGcompositeOperationState compositeOperation, + NVGscissor* scissor, + float fringe, + float strokeWidth, + const NVGpath* paths, + int npaths) { + Call* call = allocCall(); + + if (call == NULL) + return; + + call->type = MNVG_STROKE; + call->image = paint->image; + call->blendFunc = blendCompositeOperation(compositeOperation); + + // Allocate vertices for all the paths. + int strokeCount = 0; + int maxverts = maxVertexCount(paths, npaths, NULL, &strokeCount); + int offset = allocVerts(maxverts); + if (offset == -1) { + // We get here if call alloc was ok, but something else is not. + // Roll back the last call to prevent drawing it. + if (curBuffers_->ncalls > 0) + curBuffers_->ncalls--; + return; + } + + call->strokeOffset = offset + 1; + call->strokeCount = strokeCount - 2; + NVGvertex* strokeVert = curBuffers_->verts.data() + offset; + + NVGpath* path = (NVGpath*)&paths[0]; + for (int i = npaths; i--; ++path) { + if (path->nstroke > 0) { + memcpy(strokeVert, path->stroke, sizeof(NVGvertex)); + ++strokeVert; + memcpy(strokeVert, path->stroke, sizeof(NVGvertex) * path->nstroke); + strokeVert += path->nstroke; + memcpy(strokeVert, path->stroke + path->nstroke - 1, sizeof(NVGvertex)); + ++strokeVert; + } + } + + if (flags_ & NVG_STENCIL_STROKES) { + // Fill shader + call->uboIndex = allocFragUniforms(fragmentUniformBufferSize_); + convertPaintForFrag( + (FragmentUniforms*)call->uboIndex.data, paint, scissor, strokeWidth, fringe, -1.0f); + call->uboIndex2 = allocFragUniforms(fragmentUniformBufferSize_); + convertPaintForFrag((FragmentUniforms*)call->uboIndex2.data, + paint, + scissor, + strokeWidth, + fringe, + (1.0f - 0.5f / 255.0f)); + } else { + // Fill shader + call->uboIndex = allocFragUniforms(fragmentUniformBufferSize_); + convertPaintForFrag( + (FragmentUniforms*)call->uboIndex.data, paint, scissor, strokeWidth, fringe, -1.0f); + } + } + + void renderTrianglesWithPaint(NVGpaint* paint, + NVGcompositeOperationState compositeOperation, + NVGscissor* scissor, + const NVGvertex* verts, + int nverts, + float fringe) { + Call* call = allocCall(); + + if (call == NULL) + return; + + call->type = MNVG_TRIANGLES; + call->image = paint->image; + call->blendFunc = blendCompositeOperation(compositeOperation); + + // Allocate vertices for all the paths. + call->triangleOffset = allocVerts(nverts); + if (call->triangleOffset == -1) { + // We get here if call alloc was ok, but something else is not. + // Roll back the last call to prevent drawing it. + if (curBuffers_->ncalls > 0) + curBuffers_->ncalls--; + return; + } + call->triangleCount = nverts; + + memcpy(&curBuffers_->verts[call->triangleOffset], verts, sizeof(NVGvertex) * nverts); + + // Fill shader + call->uboIndex = allocFragUniforms(fragmentUniformBufferSize_); + convertPaintForFrag( + (FragmentUniforms*)call->uboIndex.data, paint, scissor, 1.0f, fringe, -1.0f); + ((FragmentUniforms*)call->uboIndex.data)->type = MNVG_SHADER_IMG; + } + + int renderUpdateTextureWithImage(int image, + int x, + int y, + int width, + int height, + const unsigned char* data) { + std::shared_ptr tex = findTexture(image); + + if (tex == nullptr) + return 0; + + unsigned char* bytes = NULL; + int bytesPerRow = 0; + if (tex->type == NVG_TEXTURE_RGBA) { + bytesPerRow = tex->tex->getSize().width * 4; + bytes = (unsigned char*)data + y * bytesPerRow + x * 4; + } else { + bytesPerRow = tex->tex->getSize().width; + bytes = (unsigned char*)data + y * bytesPerRow + x; + } + + std::shared_ptr texture = tex->tex; + igl::TextureRangeDesc desc = igl::TextureRangeDesc::new2D(x, y, width, height); + texture->upload(desc, bytes, bytesPerRow); + + return 1; + } + + int bufferIndex = 0; + + void renderViewportWithWidth(float width, float height, float device_PixelRatio) { + viewPortSize_.x = (uint32_t)(width * device_PixelRatio); + viewPortSize_.y = (uint32_t)(height * device_PixelRatio); + + bufferIndex = (bufferIndex + 1) % 3; + curBuffers_ = allBuffers_[bufferIndex]; + + curBuffers_->vertexUniforms.viewSize[0] = width; + curBuffers_->vertexUniforms.viewSize[1] = height; + + // Initializes view size buffer for vertex function. + if (curBuffers_->vertexUniformBuffer == nullptr) { + igl::BufferDesc desc(igl::BufferDesc::BufferTypeBits::Uniform, + &curBuffers_->vertexUniforms, + sizeof(VertexUniforms), + igl::ResourceStorage::Shared); + desc.hint = igl::BufferDesc::BufferAPIHintBits::UniformBlock; + desc.debugName = "vertex_uniform_buffer"; + curBuffers_->vertexUniformBuffer = device_->createBuffer(desc, NULL); + } + } + + void setUniforms(const UniformBufferIndex& uboIndex, int image) { + renderEncoder_->bindBuffer( + kFragmentUniformBlockIndex, uboIndex.buffer, uboIndex.offset, fragmentUniformBufferSize_); + + std::shared_ptr tex = (image == 0 ? nullptr : findTexture(image)); + if (tex != nullptr) { + renderEncoder_->bindTexture(0, igl::BindTarget::kFragment, tex->tex.get()); + renderEncoder_->bindSamplerState(0, igl::BindTarget::kFragment, tex->sampler.get()); + } else { + renderEncoder_->bindTexture(0, igl::BindTarget::kFragment, pseudoTexture_.get()); + renderEncoder_->bindSamplerState(0, igl::BindTarget::kFragment, pseudoSampler_.get()); + } + } + + void stroke(Call* call) { + if (call->strokeCount <= 0) { + return; + } + + if (flags_ & NVG_STENCIL_STROKES) { + // Fills the stroke base without overlap. + bindRenderPipeline(pipelineStateTriangleStrip_); + setUniforms(call->uboIndex2, call->image); + renderEncoder_->bindDepthStencilState(strokeShapeStencilState_); + + renderEncoder_->draw(call->strokeCount, 1, call->strokeOffset); + + // Draws anti-aliased fragments. + setUniforms(call->uboIndex, call->image); + renderEncoder_->bindDepthStencilState(strokeAntiAliasStencilState_); + renderEncoder_->draw(call->strokeCount, 1, call->strokeOffset); + + // Clears stencil buffer. + bindRenderPipeline(stencilOnlyPipelineStateTriangleStrip_); + renderEncoder_->bindDepthStencilState(strokeClearStencilState_); + renderEncoder_->draw(call->strokeCount, 1, call->strokeOffset); + renderEncoder_->bindDepthStencilState(defaultStencilState_); + } else { + // Draws strokes. + bindRenderPipeline(pipelineStateTriangleStrip_); + setUniforms(call->uboIndex, call->image); + renderEncoder_->draw(call->strokeCount, 1, call->strokeOffset); + } + } + + void triangles(Call* call) { + bindRenderPipeline(pipelineState_); + setUniforms(call->uboIndex, call->image); + renderEncoder_->draw(call->triangleCount, 1, call->triangleOffset); + } + + void updateRenderPipelineStatesForBlend(Blend* blend) { + if (pipelineState_ != nullptr && stencilOnlyPipelineState_ != nullptr && + piplelinePixelFormat_ == framebuffer_->getColorAttachment(0)->getProperties().format && + blendFunc_->srcRGB == blend->srcRGB && blendFunc_->dstRGB == blend->dstRGB && + blendFunc_->srcAlpha == blend->srcAlpha && blendFunc_->dstAlpha == blend->dstAlpha) { + return; + } + + igl::Result result; + + igl::RenderPipelineDesc pipelineStateDescriptor; + + pipelineStateDescriptor.fragmentUnitSamplerMap[0] = IGL_NAMEHANDLE("textureUnit"); + pipelineStateDescriptor.uniformBlockBindingMap[kVertexUniformBlockIndex] = { + std::make_pair(IGL_NAMEHANDLE("VertexUniformBlock"), igl::NameHandle{})}; + pipelineStateDescriptor.uniformBlockBindingMap[kFragmentUniformBlockIndex] = { + std::make_pair(IGL_NAMEHANDLE("FragmentUniformBlock"), igl::NameHandle{})}; + + pipelineStateDescriptor.targetDesc.colorAttachments.resize(1); + igl::RenderPipelineDesc::TargetDesc::ColorAttachment& colorAttachmentDescriptor = + pipelineStateDescriptor.targetDesc.colorAttachments[0]; + colorAttachmentDescriptor.textureFormat = + framebuffer_->getColorAttachment(0)->getProperties().format; + pipelineStateDescriptor.targetDesc.stencilAttachmentFormat = + framebuffer_->getStencilAttachment()->getProperties().format; + pipelineStateDescriptor.targetDesc.depthAttachmentFormat = + framebuffer_->getDepthAttachment()->getProperties().format; + pipelineStateDescriptor.shaderStages = igl::ShaderStagesCreator::fromRenderModules( + *device_, vertexFunction_, fragmentFunction_, &result); + IGL_DEBUG_ASSERT(result.isOk()); + + pipelineStateDescriptor.vertexInputState = + device_->createVertexInputState(vertexDescriptor_, &result); + IGL_DEBUG_ASSERT(result.isOk()); + + // Sets blending states. + colorAttachmentDescriptor.blendEnabled = true; + colorAttachmentDescriptor.srcRGBBlendFactor = blend->srcRGB; + colorAttachmentDescriptor.srcAlphaBlendFactor = blend->srcAlpha; + colorAttachmentDescriptor.dstRGBBlendFactor = blend->dstRGB; + colorAttachmentDescriptor.dstAlphaBlendFactor = blend->dstAlpha; + blendFunc_->srcRGB = blend->srcRGB; + blendFunc_->dstRGB = blend->dstRGB; + blendFunc_->srcAlpha = blend->srcAlpha; + blendFunc_->dstAlpha = blend->dstAlpha; + + pipelineStateDescriptor.topology = igl::PrimitiveType::Triangle; + pipelineStateDescriptor.cullMode = igl::CullMode::Disabled; + pipelineStateDescriptor.debugName = igl::genNameHandle("Triangle_CullNone"); + pipelineState_ = device_->createRenderPipeline(pipelineStateDescriptor, &result); + + pipelineStateDescriptor.topology = igl::PrimitiveType::TriangleStrip; + pipelineStateDescriptor.cullMode = igl::CullMode::Back; + pipelineStateDescriptor.debugName = igl::genNameHandle("TriangleStripe_CullBack"); + pipelineStateTriangleStrip_ = device_->createRenderPipeline(pipelineStateDescriptor, &result); + IGL_DEBUG_ASSERT(result.isOk()); + + auto fragmentFunction = + device_->getBackendType() == igl::BackendType::Metal ? nullptr : fragmentFunction_; + pipelineStateDescriptor.shaderStages = igl::ShaderStagesCreator::fromRenderModules( + *device_, vertexFunction_, fragmentFunction, &result); + + IGL_DEBUG_ASSERT(result.isOk()); + colorAttachmentDescriptor.colorWriteMask = igl::ColorWriteBits::ColorWriteBitsDisabled; + pipelineStateDescriptor.cullMode = igl::CullMode::Disabled; + pipelineStateDescriptor.topology = igl::PrimitiveType::Triangle; + pipelineStateDescriptor.debugName = igl::genNameHandle("stencilOnlyPipelineState"); + stencilOnlyPipelineState_ = device_->createRenderPipeline(pipelineStateDescriptor, &result); + IGL_DEBUG_ASSERT(result.isOk()); + + pipelineStateDescriptor.debugName = igl::genNameHandle("stencilOnlyPipelineStateTriangleStrip"); + pipelineStateDescriptor.topology = igl::PrimitiveType::TriangleStrip; + stencilOnlyPipelineStateTriangleStrip_ = + device_->createRenderPipeline(pipelineStateDescriptor, &result); + IGL_DEBUG_ASSERT(result.isOk()); + + piplelinePixelFormat_ = framebuffer_->getColorAttachment(0)->getProperties().format; + } +}; + +static void callback__renderCancel(void* uptr) { + Context* mtl = (Context*)uptr; + mtl->renderCancel(); +} + +static int callback__renderCreateTexture(void* uptr, + int type, + int width, + int height, + int imageFlags, + const unsigned char* data) { + Context* mtl = (Context*)uptr; + return mtl->renderCreateTextureWithType(type, width, height, imageFlags, data); +} + +static int callback__renderCreate(void* uptr) { + Context* mtl = (Context*)uptr; + return mtl->renderCreate(); +} + +static void callback__renderDelete(void* uptr) { + Context* mtl = (Context*)uptr; + mtl->renderDelete(); +} + +static int callback__renderDeleteTexture(void* uptr, int image) { + Context* mtl = (Context*)uptr; + return mtl->renderDeleteTexture(image); +} + +static void callback__renderFill(void* uptr, + NVGpaint* paint, + NVGcompositeOperationState compositeOperation, + NVGscissor* scissor, + float fringe, + const float* bounds, + const NVGpath* paths, + int npaths) { + Context* mtl = (Context*)uptr; + mtl->renderFillWithPaint(paint, compositeOperation, scissor, fringe, bounds, paths, npaths); +} + +static void callback__renderFlush(void* uptr) { + Context* mtl = (Context*)uptr; + mtl->renderFlush(); +} + +static int callback__renderGetTextureSize(void* uptr, int image, int* w, int* h) { + Context* mtl = (Context*)uptr; + return mtl->renderGetTextureSizeForImage(image, w, h); +} + +static void callback__renderStroke(void* uptr, + NVGpaint* paint, + NVGcompositeOperationState compositeOperation, + NVGscissor* scissor, + float fringe, + float strokeWidth, + const NVGpath* paths, + int npaths) { + Context* mtl = (Context*)uptr; + mtl->renderStrokeWithPaint( + paint, compositeOperation, scissor, fringe, strokeWidth, paths, npaths); +} + +static void callback__renderTriangles(void* uptr, + NVGpaint* paint, + NVGcompositeOperationState compositeOperation, + NVGscissor* scissor, + const NVGvertex* verts, + int nverts, + float fringe) { + Context* mtl = (Context*)uptr; + mtl->renderTrianglesWithPaint(paint, compositeOperation, scissor, verts, nverts, fringe); +} + +static int callback__renderUpdateTexture(void* uptr, + int image, + int x, + int y, + int w, + int h, + const unsigned char* data) { + Context* mtl = (Context*)uptr; + return mtl->renderUpdateTextureWithImage(image, x, y, w, h, data); +} + +static void callback__renderViewport(void* uptr, + float width, + float height, + float device_PixelRatio) { + Context* mtl = (Context*)uptr; + mtl->renderViewportWithWidth(width, height, device_PixelRatio); +} + +void SetRenderCommandEncoder(NVGcontext* ctx, + igl::IFramebuffer* framebuffer, + igl::IRenderCommandEncoder* command, + float* matrix) { + Context* mtl = (Context*)nvgInternalParams(ctx)->userPtr; + mtl->framebuffer_ = framebuffer; + mtl->renderEncoder_ = command; + if (matrix) { + memcpy(&mtl->curBuffers_->vertexUniforms.matrix, matrix, sizeof(float) * 16); + } +} + +NVGcontext* CreateContext(igl::IDevice* device, int flags) { + NVGparams params; + NVGcontext* ctx = NULL; + Context* mtl = new Context(); + + memset(¶ms, 0, sizeof(params)); + params.renderCreate = callback__renderCreate; + params.renderCreateTexture = callback__renderCreateTexture; + params.renderDeleteTexture = callback__renderDeleteTexture; + params.renderUpdateTexture = callback__renderUpdateTexture; + params.renderGetTextureSize = callback__renderGetTextureSize; + params.renderViewport = callback__renderViewport; + params.renderCancel = callback__renderCancel; + params.renderFlush = callback__renderFlush; + params.renderFill = callback__renderFill; + params.renderStroke = callback__renderStroke; + params.renderTriangles = callback__renderTriangles; + params.renderDelete = callback__renderDelete; + params.userPtr = (void*)mtl; + params.edgeAntiAlias = flags & NVG_ANTIALIAS ? 1 : 0; + + mtl->flags_ = flags; + + device->getFeatureLimits(igl::DeviceFeatureLimits::MaxUniformBufferBytes, + mtl->maxUniformBufferSize_); + + mtl->maxUniformBufferSize_ = std::min(mtl->maxUniformBufferSize_, (size_t)512 * 1024); + + size_t uniformBufferAlignment = 16; + device->getFeatureLimits(igl::DeviceFeatureLimits::BufferAlignment, uniformBufferAlignment); + // sizeof(MNVGfragUniforms)= 176 + // 64 * 3 > 176 + mtl->fragmentUniformBufferSize_ = std::max(64 * 3, (int)uniformBufferAlignment); + + mtl->indexSize_ = 4; // IndexType::UInt32 + mtl->device_ = device; + + ctx = nvgCreateInternal(¶ms); + if (ctx == NULL) + goto error; + return ctx; + +error: + if (ctx != NULL) + nvgDeleteInternal(ctx); + return NULL; +} + +void DestroyContext(NVGcontext* ctx) { + if (!ctx) + return; + nvgDeleteInternal(ctx); + + if (auto param = nvgInternalParams(ctx)) { + if (param->userPtr) { + delete (Context*)param->userPtr; + } + } +} + +} // namespace iglu::nanovg diff --git a/IGLU/nanovg/nanovg_igl.h b/IGLU/nanovg/nanovg_igl.h new file mode 100644 index 0000000000..6017d952ac --- /dev/null +++ b/IGLU/nanovg/nanovg_igl.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#pragma once +#include "nanovg.h" +#include + +namespace iglu::nanovg { + +/* + * Create flags + */ +enum NVGcreateFlags { + /* + * Flag indicating if geometry based anti-aliasing is used (may not be needed when using MSAA). + */ + NVG_ANTIALIAS = 1 << 0, + /* + * Flag indicating if strokes should be drawn using stencil buffer. + * The rendering will be a little slower, but path overlaps + * (i.e. self-intersecting or sharp turns) will be drawn just once. + */ + NVG_STENCIL_STROKES = 1 << 1, + /* + * Flag indicating if double buffering scheme is used. + * Flag indicating that additional debug checks are done. + */ + NVG_DEBUG = 1 << 2, +}; + +/* + * These are additional flags on top of NVGimageFlags. + */ +enum NVGimageFlags { + /* + * Do not delete texture handle. + */ + NVG_IMAGE_NODELETE = 1 << 16, +}; + +/* + * Creates a new NanoVG context. The `flags` should be combination of `NVGcreateFlags` above. + */ +NVGcontext* CreateContext(igl::IDevice* device, int flags); + +/* + * Set RenderCommandEncoder form outside. + * @param matrix , use outside matrix, for example : vulkan preRotate matrix. + */ +void SetRenderCommandEncoder(NVGcontext* ctx, + igl::IFramebuffer* framebuffer, + igl::IRenderCommandEncoder*, + float* matrix); + +/* + * Deletes the specified NanoVG context. + */ +void DestroyContext(NVGcontext* ctx); + +} // namespace iglu::nanovg diff --git a/IGLU/nanovg/shader_metal.h b/IGLU/nanovg/shader_metal.h new file mode 100644 index 0000000000..3dd48a4730 --- /dev/null +++ b/IGLU/nanovg/shader_metal.h @@ -0,0 +1,167 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +#pragma once +#include + +namespace iglu::nanovg { + +static std::string metalShader = R"( +#include +#include + +using namespace metal; + +typedef struct { + float2 pos [[attribute(0)]]; + float2 tcoord [[attribute(1)]]; +} Vertex; + +typedef struct { + float4 pos [[position]]; + float2 fpos; + float2 ftcoord; +} RasterizerData; + +typedef struct { + float4x4 matrix; + float2 viewSize; +} VertexUniforms; + +typedef struct { + float3x3 scissorMat; + float3x3 paintMat; + float4 innerCol; + float4 outerCol; + float2 scissorExt; + float2 scissorScale; + float2 extent; + float radius; + float feather; + float strokeMult; + float strokeThr; + int texType; + int type; +} FragmentUniforms; + +float scissorMask(constant FragmentUniforms& uniforms, float2 p); +float sdroundrect(constant FragmentUniforms& uniforms, float2 pt); +float strokeMask(constant FragmentUniforms& uniforms, float2 ftcoord); + +float scissorMask(constant FragmentUniforms& uniforms, float2 p) { + float2 sc = (abs((uniforms.scissorMat * float3(p, 1.0f)).xy) + - uniforms.scissorExt) \ + * uniforms.scissorScale; + sc = saturate(float2(0.5f) - sc); + return sc.x * sc.y; +} + +float sdroundrect(constant FragmentUniforms& uniforms, float2 pt) { + float2 ext2 = uniforms.extent - float2(uniforms.radius); + float2 d = abs(pt) - ext2; + return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - uniforms.radius; +} + +float strokeMask(constant FragmentUniforms& uniforms, float2 ftcoord) { + return min(1.0, (1.0 - abs(ftcoord.x * 2.0 - 1.0)) * uniforms.strokeMult) \ + * min(1.0, ftcoord.y); +} + +// Vertex Function +vertex RasterizerData vertexShader(Vertex vert [[stage_in]], + constant VertexUniforms& uniforms [[buffer(1)]]) { + RasterizerData out; + out.ftcoord = vert.tcoord; + out.fpos = vert.pos; + out.pos = float4(2.0 * vert.pos.x / uniforms.viewSize.x - 1.0, + 1.0 - 2.0 * vert.pos.y / uniforms.viewSize.y, + 0, 1); + out.pos = uniforms.matrix * out.pos; + return out; +} + +// Fragment function (No AA) +fragment float4 fragmentShader(RasterizerData in [[stage_in]], + constant FragmentUniforms& uniforms [[buffer(2)]], + texture2d texture [[texture(0)]], + sampler sampler [[sampler(0)]]) { + float scissor = scissorMask(uniforms, in.fpos); + if (scissor == 0) + return float4(0); + + if (uniforms.type == 0) { // MNVG_SHADER_FILLGRAD + float2 pt = (uniforms.paintMat * float3(in.fpos, 1.0)).xy; + float d = saturate((uniforms.feather * 0.5 + sdroundrect(uniforms, pt)) + / uniforms.feather); + float4 color = mix(uniforms.innerCol, uniforms.outerCol, d); + return color * scissor; + } else if (uniforms.type == 1) { // MNVG_SHADER_FILLIMG + float2 pt = (uniforms.paintMat * float3(in.fpos, 1.0)).xy / uniforms.extent; + float4 color = texture.sample(sampler, pt); + if (uniforms.texType == 1) + color = float4(color.xyz * color.w, color.w); + else if (uniforms.texType == 2) + color = float4(color.x); + color *= scissor; + return color * uniforms.innerCol; + } else { // MNVG_SHADER_IMG + float4 color = texture.sample(sampler, in.ftcoord); + if (uniforms.texType == 1) + color = float4(color.xyz * color.w, color.w); + else if (uniforms.texType == 2) + color = float4(color.x); + color *= scissor; + return color * uniforms.innerCol; + } +} + +// Fragment function (AA) +fragment float4 fragmentShaderAA(RasterizerData in [[stage_in]], + constant FragmentUniforms& uniforms [[buffer(2)]], + texture2d texture [[texture(0)]], + sampler sampler [[sampler(0)]]) { + float scissor = scissorMask(uniforms, in.fpos); + if (scissor == 0) + return float4(0); + + if (uniforms.type == 2) { // MNVG_SHADER_IMG + float4 color = texture.sample(sampler, in.ftcoord); + if (uniforms.texType == 1) + color = float4(color.xyz * color.w, color.w); + else if (uniforms.texType == 2) + color = float4(color.x); + color *= scissor; + return color * uniforms.innerCol; + } + + float strokeAlpha = strokeMask(uniforms, in.ftcoord); + if (strokeAlpha < uniforms.strokeThr) { + return float4(0); + } + + if (uniforms.type == 0) { // MNVG_SHADER_FILLGRAD + float2 pt = (uniforms.paintMat * float3(in.fpos, 1.0)).xy; + float d = saturate((uniforms.feather * 0.5 + sdroundrect(uniforms, pt)) + / uniforms.feather); + float4 color = mix(uniforms.innerCol, uniforms.outerCol, d); + color *= scissor; + color *= strokeAlpha; + return color; + } else { // MNVG_SHADER_FILLIMG + float2 pt = (uniforms.paintMat * float3(in.fpos, 1.0)).xy / uniforms.extent; + float4 color = texture.sample(sampler, pt); + if (uniforms.texType == 1) + color = float4(color.xyz * color.w, color.w); + else if (uniforms.texType == 2) + color = float4(color.x); + color *= scissor; + color *= strokeAlpha; + return color * uniforms.innerCol; + } +} +)"; + +} diff --git a/IGLU/nanovg/shader_opengl.h b/IGLU/nanovg/shader_opengl.h new file mode 100644 index 0000000000..9839c6f144 --- /dev/null +++ b/IGLU/nanovg/shader_opengl.h @@ -0,0 +1,226 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +#pragma once +#include + +namespace iglu::nanovg { + +static std::string openglVertexShaderHeader410 = R"(#version 410 +layout(location = 0) in vec2 pos; +layout(location = 1) in vec2 tcoord; + +out vec2 fpos; +out vec2 ftcoord; + +layout(std140) uniform VertexUniformBlock { + mat4 matrix; + vec2 viewSize; +}uniforms; +)"; + +static std::string openglVertexShaderHeader460 = R"(#version 460 +layout(location = 0) in vec2 pos; +layout(location = 1) in vec2 tcoord; + +layout (location=0) out vec2 fpos; +layout (location=1) out vec2 ftcoord; + +layout(set = 1, binding = 1, std140) uniform VertexUniformBlock { + mat4 matrix; + vec2 viewSize; +}uniforms; +)"; + +static std::string openglVertexShaderBody = R"( +void main() { + ftcoord = tcoord; + fpos = pos; + gl_Position = vec4(2.0 * pos.x / uniforms.viewSize.x - 1.0, + 1.0 - 2.0 * pos.y / uniforms.viewSize.y, + 0, 1); + gl_Position = uniforms.matrix * gl_Position; +} +)"; + +static std::string openglFragmentShaderHeader410 = R"(#version 410 +precision highp int; +precision highp float; + +in vec2 fpos; +in vec2 ftcoord; + +layout (location=0) out vec4 FragColor; + +uniform lowp sampler2D textureUnit; + +layout(std140) uniform FragmentUniformBlock { + mat3 scissorMat; + mat3 paintMat; + vec4 innerCol; + vec4 outerCol; + vec2 scissorExt; + vec2 scissorScale; + vec2 extent; + float radius; + float feather; + float strokeMult; + float strokeThr; + int texType; + int type; +}uniforms; + +)"; + +static std::string openglFragmentShaderHeader460 = R"(#version 460 +precision highp int; +precision highp float; + +layout (location=0) in vec2 fpos; +layout (location=1) in vec2 ftcoord; + +layout (location=0) out vec4 FragColor; + +layout(set = 0, binding = 0) uniform lowp sampler2D textureUnit; + +layout(set = 1, binding = 2, std140) uniform FragmentUniformBlock { + mat3 scissorMat; + mat3 paintMat; + vec4 innerCol; + vec4 outerCol; + vec2 scissorExt; + vec2 scissorScale; + vec2 extent; + float radius; + float feather; + float strokeMult; + float strokeThr; + int texType; + int type; +}uniforms; +)"; + +static std::string openglNoAntiAliasingFragmentShaderBody = R"( +float scissorMask(vec2 p) { + vec2 sc = (abs((uniforms.scissorMat * vec3(p, 1.0f)).xy) + - uniforms.scissorExt) * uniforms.scissorScale; + sc = clamp(vec2(0.5f) - sc, 0.0, 1.0); + return sc.x * sc.y; +} + +float sdroundrect(vec2 pt) { + vec2 ext2 = uniforms.extent - vec2(uniforms.radius); + vec2 d = abs(pt) - ext2; + return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - uniforms.radius; +} + +float strokeMask(vec2 ftcoord) { + return min(1.0, (1.0 - abs(ftcoord.x * 2.0 - 1.0)) * uniforms.strokeMult) * min(1.0, ftcoord.y); +} + +vec4 fragmentShaderNoAntiAliasing() { + float scissor = scissorMask(fpos); + if (scissor == 0.0) + return vec4(0); + + if (uniforms.type == 0) { // MNVG_SHADER_FILLGRAD + vec2 pt = (uniforms.paintMat * vec3(fpos, 1.0)).xy; + float d = clamp((uniforms.feather * 0.5 + sdroundrect(pt)) + / uniforms.feather, 0.0, 1.0); + vec4 color = mix(uniforms.innerCol, uniforms.outerCol, d); + return color * scissor; + } else if (uniforms.type == 1) { // MNVG_SHADER_FILLIMG + vec2 pt = (uniforms.paintMat * vec3(fpos, 1.0)).xy / uniforms.extent; + vec4 color = texture(textureUnit, pt); + if (uniforms.texType == 1) + color = vec4(color.xyz * color.w, color.w); + else if (uniforms.texType == 2) + color = vec4(color.x); + color *= scissor; + return color * uniforms.innerCol; + } else { // MNVG_SHADER_IMG + vec4 color = texture(textureUnit, ftcoord); + if (uniforms.texType == 1) + color = vec4(color.xyz * color.w, color.w); + else if (uniforms.texType == 2) + color = vec4(color.x); + color *= scissor; + return color * uniforms.innerCol; + } +} + +void main(){ + FragColor = fragmentShaderNoAntiAliasing(); +} + +)"; + +static std::string openglAntiAliasingFragmentShaderBody = R"( +float scissorMask(vec2 p) { + vec2 sc = (abs((uniforms.scissorMat * vec3(p, 1.0f)).xy) + - uniforms.scissorExt) * uniforms.scissorScale; + sc = clamp(vec2(0.5f) - sc, 0.0, 1.0); + return sc.x * sc.y; +} + +float sdroundrect(vec2 pt) { + vec2 ext2 = uniforms.extent - vec2(uniforms.radius); + vec2 d = abs(pt) - ext2; + return min(max(d.x, d.y), 0.0) + length(max(d, 0.0)) - uniforms.radius; +} + +float strokeMask(vec2 ftcoord) { + return min(1.0, (1.0 - abs(ftcoord.x * 2.0 - 1.0)) * uniforms.strokeMult) * min(1.0, ftcoord.y); +} + +vec4 fragmentShaderAntiAliasing() { + float scissor = scissorMask(fpos); + if (scissor == 0.0) + return vec4(0); + + if (uniforms.type == 2) { // MNVG_SHADER_IMG + vec4 color = texture(textureUnit, ftcoord); + if (uniforms.texType == 1) + color = vec4(color.xyz * color.w, color.w); + else if (uniforms.texType == 2) + color = vec4(color.x); + color *= scissor; + return color * uniforms.innerCol; + } + + float strokeAlpha = strokeMask(ftcoord); + if (strokeAlpha < uniforms.strokeThr) { + return vec4(0); + } + + if (uniforms.type == 0) { // MNVG_SHADER_FILLGRAD + vec2 pt = (uniforms.paintMat * vec3(fpos, 1.0)).xy; + float d = clamp((uniforms.feather * 0.5 + sdroundrect(pt)) + / uniforms.feather, 0.0, 1.0); + vec4 color = mix(uniforms.innerCol, uniforms.outerCol, d); + color *= scissor; + color *= strokeAlpha; + return color; + } else { // MNVG_SHADER_FILLIMG + vec2 pt = (uniforms.paintMat * vec3(fpos, 1.0)).xy / uniforms.extent; + vec4 color = texture(textureUnit, pt); + if (uniforms.texType == 1) + color = vec4(color.xyz * color.w, color.w); + else if (uniforms.texType == 2) + color = vec4(color.x); + color *= scissor; + color *= strokeAlpha; + return color * uniforms.innerCol; + } +} + +void main(){ + FragColor = fragmentShaderAntiAliasing(); +} + +)"; + +} // namespace iglu::nanovg diff --git a/build/android/nanovg/build.gradle b/build/android/nanovg/build.gradle new file mode 100644 index 0000000000..240ed789e8 --- /dev/null +++ b/build/android/nanovg/build.gradle @@ -0,0 +1,75 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +plugins { + id 'com.android.application' +} + +android { + namespace 'com.facebook.igl.shell' + compileSdk 33 + + defaultConfig { + applicationId "com.facebook.igl.shell" + minSdk 33 + targetSdk 33 + versionCode 1 + versionName "1.0" + ndk { + abiFilters 'arm64-v8a' + } + externalNativeBuild { + cmake { + cppFlags '-std=c++17' + } + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + externalNativeBuild { + cmake { + path file('../../../CMakeLists.txt') + version '3.22.1' + } + } + buildFeatures { + viewBinding true + } + + sourceSets { + main { + manifest.srcFile 'src/main/AndroidManifest.xml' + res.srcDir '../app/src/main/res/' + + java.srcDir 'java' + java.srcDirs += ['../app/src/main/java'] + + assets.srcDirs += ['../../../shell/resources/images', '../../../third-party/deps/src/nanovg/example/images', '../../../third-party/deps/src/nanovg/example'] + } + } + + ndkVersion '25.2.9519653' +} + +dependencies { + + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'com.google.android.material:material:1.9.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.5' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' +} diff --git a/build/android/nanovg/proguard-rules.pro b/build/android/nanovg/proguard-rules.pro new file mode 100644 index 0000000000..f1b424510d --- /dev/null +++ b/build/android/nanovg/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/build/android/nanovg/src/main/AndroidManifest.xml b/build/android/nanovg/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..79af625c7b --- /dev/null +++ b/build/android/nanovg/src/main/AndroidManifest.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + diff --git a/build/android/nanovg/src/main/java/com/facebook/igl/sample/NanovgSampleActivity.java b/build/android/nanovg/src/main/java/com/facebook/igl/sample/NanovgSampleActivity.java new file mode 100644 index 0000000000..041c3ac925 --- /dev/null +++ b/build/android/nanovg/src/main/java/com/facebook/igl/sample/NanovgSampleActivity.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +// @fb-only + +package com.facebook.igl.shell; + +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.util.Log; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; + +public class NanovgSampleActivity extends SampleActivity { + private static String TAG = "NanovgSampleActivity"; + + @Override + protected void onCreate(Bundle icicle) { + mEnableStencilBuffer = true; + + super.onCreate(icicle); + + boolean hasCopy = getSharedPreferences("data", Context.MODE_PRIVATE).getBoolean("HasCopyAssets", false); + + if (!hasCopy) { + copyAssetsDirToSDCard(this, "", getFilesDir().getAbsolutePath()); + + SharedPreferences.Editor editor = getSharedPreferences("data", Context.MODE_PRIVATE).edit(); + editor.putBoolean("HasCopyAssets",true); + editor.commit(); + } + } + + public static void copyAssetsDirToSDCard(Context context, String assetsDirName, String sdCardPath) { + Log.d(TAG, "copyAssetsDirToSDCard() called with: context = [" + context + "], assetsDirName = [" + assetsDirName + "], sdCardPath = [" + sdCardPath + "]"); + try { + String list[] = context.getAssets().list(assetsDirName); + if (list.length == 0) { + InputStream inputStream = context.getAssets().open(assetsDirName); + byte[] mByte = new byte[1024]; + int bt = 0; + File file = new File(sdCardPath + File.separator + + assetsDirName); + if (!file.exists()) { + file.createNewFile(); + } else { + return; + } + FileOutputStream fos = new FileOutputStream(file); + while ((bt = inputStream.read(mByte)) != -1) { + fos.write(mByte, 0, bt); + } + fos.flush(); + inputStream.close(); + fos.close(); + } else { + String subDirName = assetsDirName; + if (assetsDirName.contains("/")) { + subDirName = assetsDirName.substring(assetsDirName.lastIndexOf('/') + 1); + } + sdCardPath = sdCardPath + File.separator + subDirName; + File file = new File(sdCardPath); + if (!file.exists()) + file.mkdirs(); + for (String s : list) { + String fileName = assetsDirName.length() > 0 ? assetsDirName + File.separator + s : s; + copyAssetsDirToSDCard(context, fileName , sdCardPath); + } + } + } catch ( + Exception e) { + e.printStackTrace(); + } + } + +} diff --git a/build/android/settings.gradle b/build/android/settings.gradle index 21aed34696..1c41b602b9 100644 --- a/build/android/settings.gradle +++ b/build/android/settings.gradle @@ -21,6 +21,7 @@ dependencyResolutionManagement { } rootProject.name = "IGL" include ':app' +include ':nanovg' include ':app-openxr-vulkan' include ':app-openxr-gles' diff --git a/shell/CMakeLists.txt b/shell/CMakeLists.txt index 593f7b06ec..08e45ca48a 100644 --- a/shell/CMakeLists.txt +++ b/shell/CMakeLists.txt @@ -63,6 +63,11 @@ macro(ADD_SHELL_SESSION target libs) add_shell_session_with_srcs(${target} "${shell_srcs}" "${libs}") endmacro() +macro(ADD_SHELL_SESSION_SRC target srcs libs) + set(shell_srcs ${srcs} apps/SessionApp.cpp renderSessions/${target}.cpp renderSessions/${target}.h) + add_shell_session_with_srcs(${target} "${shell_srcs}" "${libs}") +endmacro() + macro(ADD_SHELL_SESSION_OPENXR_SIM target libs) set(shell_srcs apps/SessionApp.cpp renderSessions/${target}.cpp renderSessions/${target}.h) if(WIN32) @@ -96,6 +101,28 @@ if(IGL_WITH_SAMPLES) add_shell_session(TQMultiRenderPassSession "") add_shell_session(TQSession "") add_shell_session(YUVColorSession "") + if (IGLU_WITH_NANOVG) + set(nanovg_demo_cpp ${IGL_ROOT_DIR}/third-party/deps/src/nanovg/example/demo.c + ${IGL_ROOT_DIR}/third-party/deps/src/nanovg/example/perf.c ) + add_shell_session_src(NanovgSession "${nanovg_demo_cpp}" "IGLUnanovg") + + set(nanovg_demo_include ${IGL_ROOT_DIR}/third-party/deps/src/nanovg/example) + + if(WIN32 OR UNIX AND NOT APPLE AND NOT ANDROID) + if(IGL_WITH_VULKAN) + target_include_directories(NanovgSession_vulkan PUBLIC ${nanovg_demo_include}) + endif() + if(IGL_WITH_OPENGL) + target_include_directories(NanovgSession_opengl PUBLIC ${nanovg_demo_include}) + endif() + if(IGL_WITH_OPENGLES) + target_include_directories(NanovgSession_opengles PUBLIC ${nanovg_demo_include}) + endif() + else() + target_include_directories(NanovgSession PUBLIC ${nanovg_demo_include}) + endif() + + endif() if(IGL_WITH_OPENXR) if(ANDROID) add_shell_session(HelloOpenXRSession "") diff --git a/shell/ios/CMakeLists.txt b/shell/ios/CMakeLists.txt index 8254e2823d..adf2b7318d 100644 --- a/shell/ios/CMakeLists.txt +++ b/shell/ios/CMakeLists.txt @@ -30,7 +30,9 @@ function(ADD_SHELL_SESSION_WITH_SRCS target srcs libs) file(GLOB PLATFORM_SHELL_SRC_FILES LIST_DIRECTORIES false ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/*.mm ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/*.m) file(GLOB PLATFORM_SHELL_HEADER_FILES LIST_DIRECTORIES false ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/*.h) - file(GLOB RESOURCES LIST_DIRECTORIES false ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../resources/images/*.png) + file(GLOB RESOURCES LIST_DIRECTORIES false ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../resources/images/*.png + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../../third-party/deps/src/nanovg/example/images/*.jpg + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../../third-party/deps/src/nanovg/example/*.ttf) add_executable(${target} MACOSX_BUNDLE ${srcs} "${PLATFORM_SHELL_SRC_FILES}" "${PLATFORM_SHELL_HEADER_FILES}" "${RESOURCES}") igl_set_folder(${target} "IGL Shell Sessions") diff --git a/shell/mac/AppDelegate.mm b/shell/mac/AppDelegate.mm index 16a3fb129b..b32def4347 100644 --- a/shell/mac/AppDelegate.mm +++ b/shell/mac/AppDelegate.mm @@ -109,6 +109,7 @@ - (void)setupViewController { .majorVersion = 3, .minorVersion = 0}, .swapchainColorTextureFormat = kColorFramebufferFormat, + .depthTextureFormat = igl::TextureFormat::S8_UInt_Z24_UNorm, }, #endif #if IGL_BACKEND_OPENGL @@ -118,6 +119,7 @@ - (void)setupViewController { .majorVersion = 4, .minorVersion = 1}, .swapchainColorTextureFormat = kColorFramebufferFormat, + .depthTextureFormat = igl::TextureFormat::S8_UInt_Z24_UNorm, }, // clang-format off // @fb-only diff --git a/shell/mac/CMakeLists.txt b/shell/mac/CMakeLists.txt index 3161856953..c90922bb97 100644 --- a/shell/mac/CMakeLists.txt +++ b/shell/mac/CMakeLists.txt @@ -35,7 +35,8 @@ function(ADD_SHELL_SESSION_WITH_SRCS target srcs libs) file(GLOB SHELL_SESSION_SRC_FILES LIST_DIRECTORIES false ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/*.mm ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/*.m) file(GLOB SHELL_SESSION_HEADER_FILES LIST_DIRECTORIES false ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/*.h) - file(GLOB RESOURCES LIST_DIRECTORIES false ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../resources/images/*.png) + file(GLOB RESOURCES LIST_DIRECTORIES false ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../resources/images/*.png ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../../third-party/deps/src/nanovg/example/images/*.jpg + ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/../../third-party/deps/src/nanovg/example/*.ttf) add_executable(${target} MACOSX_BUNDLE ${srcs} "${SHELL_SESSION_SRC_FILES}" "${SHELL_SESSION_SHELL_HEADER_FILES}" "${XIBFILE}" "${RESOURCES}") diff --git a/shell/renderSessions/NanovgSession.cpp b/shell/renderSessions/NanovgSession.cpp new file mode 100644 index 0000000000..b5d75017d3 --- /dev/null +++ b/shell/renderSessions/NanovgSession.cpp @@ -0,0 +1,192 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +// @fb-only + +#include "NanovgSession.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace igl::shell { + +int NanovgSession::loadDemoData(NVGcontext* vg, DemoData* data) { + auto getImageFullPath = ([this](const std::string& name) { +#if IGL_PLATFORM_ANDROID + auto fullPath = std::filesystem::path("/data/data/com.facebook.igl.shell/files/") / name; + if (std::filesystem::exists(fullPath)) { + return fullPath.string(); + } else { + IGL_DEBUG_ASSERT(false); + return std::string(""); + } +#else + return getPlatform().getImageLoader().fileLoader().fullPath(name); +#endif + }); + + if (!IGL_DEBUG_VERIFY(vg)) { + return -1; + } + + for (int i = 0; i < 12; i++) { + char file[128]; + snprintf(file, 128, "image%d.jpg", i + 1); + + std::string full_file = getImageFullPath(file); + data->images[i] = nvgCreateImage(vg, full_file.c_str(), 0); + if (!IGL_DEBUG_VERIFY(data->images[i] != 0, "Could not load %s.", file)) { + return -1; + } + } + + data->fontIcons = nvgCreateFont(vg, "icons", getImageFullPath("entypo.ttf").c_str()); + if (!IGL_DEBUG_VERIFY(data->fontIcons != -1, "Could not add font icons.")) { + return -1; + } + data->fontNormal = nvgCreateFont(vg, "sans", getImageFullPath("Roboto-Regular.ttf").c_str()); + if (!IGL_DEBUG_VERIFY(data->fontNormal != -1, "Could not add font italic.")) { + return -1; + } + data->fontBold = nvgCreateFont(vg, "sans-bold", getImageFullPath("Roboto-Bold.ttf").c_str()); + if (!IGL_DEBUG_VERIFY(data->fontBold != -1, "Could not add font bold.")) { + return -1; + } + data->fontEmoji = nvgCreateFont(vg, "emoji", getImageFullPath("NotoEmoji-Regular.ttf").c_str()); + if (!IGL_DEBUG_VERIFY(data->fontEmoji != -1, "Could not add font emoji.")) { + return -1; + } + nvgAddFallbackFontId(vg, data->fontNormal, data->fontEmoji); + nvgAddFallbackFontId(vg, data->fontBold, data->fontEmoji); + + return 0; +} + +void NanovgSession::initialize() noexcept { + const CommandQueueDesc desc; + commandQueue_ = getPlatform().getDevice().createCommandQueue(desc, nullptr); + + renderPass_.colorAttachments.resize(1); + + renderPass_.colorAttachments[0] = igl::RenderPassDesc::ColorAttachmentDesc{}; + renderPass_.colorAttachments[0].loadAction = LoadAction::Clear; + renderPass_.colorAttachments[0].storeAction = StoreAction::Store; + renderPass_.colorAttachments[0].clearColor = igl::Color(0.3f, 0.3f, 0.32f, 1.0f); + renderPass_.depthAttachment.loadAction = LoadAction::Clear; + renderPass_.depthAttachment.clearDepth = 1.0; + renderPass_.stencilAttachment.loadAction = LoadAction::Clear; + renderPass_.stencilAttachment.clearStencil = 0; + + mouseListener_ = std::make_shared(); + getPlatform().getInputDispatcher().addMouseListener(mouseListener_); + + touchListener_ = std::make_shared(); + getPlatform().getInputDispatcher().addTouchListener(touchListener_); + + nvgContext_ = iglu::nanovg::CreateContext( + &getPlatform().getDevice(), iglu::nanovg::NVG_ANTIALIAS | iglu::nanovg::NVG_STENCIL_STROKES); + + if (this->loadDemoData(nvgContext_, &nvgDemoData_) != 0) { + IGL_DEBUG_ASSERT(false); + } + + initGraph(&fps_, GRAPH_RENDER_FPS, "Frame Time"); + initGraph(&cpuGraph_, GRAPH_RENDER_MS, "CPU Time"); + initGraph(&gpuGraph_, GRAPH_RENDER_MS, "GPU Time"); + times_ = 0; +} + +void NanovgSession::update(igl::SurfaceTextures surfaceTextures) noexcept { + FramebufferDesc framebufferDesc; + framebufferDesc.colorAttachments[0].texture = surfaceTextures.color; + framebufferDesc.depthAttachment.texture = surfaceTextures.depth; + framebufferDesc.stencilAttachment.texture = surfaceTextures.depth; + + const auto dimensions = surfaceTextures.color->getDimensions(); + framebuffer_ = getPlatform().getDevice().createFramebuffer(framebufferDesc, nullptr); + IGL_DEBUG_ASSERT(framebuffer_); + framebuffer_->updateDrawable(surfaceTextures.color); + + // Command buffers (1-N per thread): create, submit and forget + const CommandBufferDesc cbDesc; + const std::shared_ptr buffer = + commandQueue_->createCommandBuffer(cbDesc, nullptr); + + // This will clear the framebuffer + std::shared_ptr commands = + buffer->createRenderCommandEncoder(renderPass_, framebuffer_); + + drawNanovg((float)dimensions.width, (float)dimensions.height, commands); + + commands->endEncoding(); + + if (shellParams().shouldPresent) { + buffer->present(surfaceTextures.color); + } + + commandQueue_->submit(*buffer); + RenderSession::update(surfaceTextures); +} + +void NanovgSession::drawNanovg(float framebuffferWidth, + float framebufferHeight, + std::shared_ptr command) { + NVGcontext* vg = nvgContext_; + + float pxRatio = 2.0f; + + const float width = framebuffferWidth / pxRatio; + const float height = framebufferHeight / pxRatio; + +#if IGL_PLATFORM_IOS || IGL_PLATFORM_ANDROID + int mx = touchListener_->touchX; + int my = touchListener_->touchY; +#else + int mx = mouseListener_->mouseX; + int my = mouseListener_->mouseY; +#endif + + auto start = getSeconds(); + + nvgBeginFrame(vg, width, height, pxRatio); + iglu::nanovg::SetRenderCommandEncoder( + vg, + framebuffer_.get(), + command.get(), + (float*)&getPlatform().getDisplayContext().preRotationMatrix); + + times_++; + + renderDemo(vg, mx, my, width, height, times_ / 60.0f, 0, &nvgDemoData_); + + renderGraph(vg, 5, 5, &fps_); + renderGraph(vg, 5 + 200 + 5, 5, &cpuGraph_); + renderGraph(vg, 5 + 200 + 5 + 200 + 5, 5, &gpuGraph_); + + nvgEndFrame(vg); + + auto end = getSeconds(); + + updateGraph(&fps_, getDeltaSeconds()); + updateGraph(&cpuGraph_, (end - start)); +} + +void NanovgSession::teardown() noexcept { + if (nvgContext_) { + iglu::nanovg::DestroyContext(nvgContext_); + } +} + +} // namespace igl::shell diff --git a/shell/renderSessions/NanovgSession.h b/shell/renderSessions/NanovgSession.h new file mode 100644 index 0000000000..70db262486 --- /dev/null +++ b/shell/renderSessions/NanovgSession.h @@ -0,0 +1,80 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +// @fb-only + +#pragma once + +#include "demo.h" +#include "perf.h" +#include +#include +#include +#include +#include +#include + +namespace igl::shell { + +class MouseListener : public IMouseListener { + public: + bool process(const MouseButtonEvent& event) override { + return true; + } + bool process(const MouseMotionEvent& event) override { + mouseX = event.x; + mouseY = event.y; + return true; + } + bool process(const MouseWheelEvent& event) override { + return true; + } + + int mouseX; + int mouseY; +}; + +class TouchListener : public ITouchListener { + public: + bool process(const TouchEvent& event) override { + touchX = event.x; + touchY = event.y; + return true; + } + + int touchX; + int touchY; +}; + +class NanovgSession : public RenderSession { + public: + explicit NanovgSession(std::shared_ptr platform) : RenderSession(std::move(platform)) {} + void initialize() noexcept override; + void update(igl::SurfaceTextures surfaceTextures) noexcept override; + void teardown() noexcept override; + + private: + void drawNanovg(float framebuffferWidth, + float framebufferHeight, + std::shared_ptr command); + int loadDemoData(NVGcontext* vg, DemoData* data); + + private: + std::shared_ptr commandQueue_; + RenderPassDesc renderPass_; + + NVGcontext* nvgContext_ = nullptr; + int times_ = 0; + DemoData nvgDemoData_; + + std::shared_ptr mouseListener_; + std::shared_ptr touchListener_; + + PerfGraph fps_, cpuGraph_, gpuGraph_; +}; + +} // namespace igl::shell diff --git a/shell/shared/fileLoader/win/FileLoaderWin.cpp b/shell/shared/fileLoader/win/FileLoaderWin.cpp index 6ca49d89a2..f56317872f 100644 --- a/shell/shared/fileLoader/win/FileLoaderWin.cpp +++ b/shell/shared/fileLoader/win/FileLoaderWin.cpp @@ -91,6 +91,8 @@ std::string FileLoaderWin::fullPath(const std::string& fileName) const { // @lint-ignore CLANGTIDY const char* folders[] = { "shell/resources/images/", + "third-party/deps/src/nanovg/example/images", + "third-party/deps/src/nanovg/example/", "samples/resources/images/", "samples/resources/models/", "samples/resources/fonts/",