From 198184e6df110afcf912dd12b7e1093eea859665 Mon Sep 17 00:00:00 2001 From: loosche Date: Fri, 15 Sep 2023 20:11:38 -0700 Subject: [PATCH] Initial checkin for Experiment 405 port to Metal The code works, but ignores any .gltf models that don't have a stride of 48 bytes. There are a few because the input file has some models without Tangent Data. Hai's gonna fix that with the source at some point and then I can clean up this branch. --- .../render_pbr_material.metal | 396 +++++++++++ projects/common/.mtl_renderer.cpp.swp | Bin 61440 -> 0 bytes projects/common/mtl_faux_render.cpp | 87 +-- projects/common/mtl_renderer.cpp | 9 +- projects/common/mtl_renderer.h | 3 +- .../405_gltf_full_material_test_metal.cpp | 660 ++++++++++++++++++ .../CMakeLists.txt | 52 ++ projects/io/CMakeLists.txt | 1 + 8 files changed, 1120 insertions(+), 88 deletions(-) create mode 100644 assets/faux_render_shaders/render_pbr_material.metal delete mode 100644 projects/common/.mtl_renderer.cpp.swp create mode 100644 projects/io/405_gltf_full_material_test_metal/405_gltf_full_material_test_metal.cpp create mode 100644 projects/io/405_gltf_full_material_test_metal/CMakeLists.txt diff --git a/assets/faux_render_shaders/render_pbr_material.metal b/assets/faux_render_shaders/render_pbr_material.metal new file mode 100644 index 00000000..010e13eb --- /dev/null +++ b/assets/faux_render_shaders/render_pbr_material.metal @@ -0,0 +1,396 @@ +#include +using namespace metal; + +#define PI 3.1415292 +#define EPSILON 0.00001 + +#define MAX_INSTANCES 100 +#define MAX_MATERIALS 100 +#define MAX_MATERIAL_SAMPLERS 32 +#define MAX_MATERIAL_IMAGES 1024 +#define MAX_IBL_TEXTURES 1 + +#define SCENE_REGISTER 4 +#define CAMERA_REGISTER 5 +#define DRAW_REGISTER 6 +#define INSTANCE_BUFFER_REGISTER 7 +#define MATERIAL_BUFFER_REGISTER 8 +#define MATERIAL_SAMPLER_START_REGISTER 9 +#define MATERIAL_IMAGES_START_REGISTER 10 +#define IBL_ENV_MAP_TEXTURE_START_REGISTER 11 +#define IBL_IRR_MAP_TEXTURE_START_REGISTER 12 +#define IBL_INTEGRATION_LUT_REGISTER 13 +#define IBL_MAP_SAMPLER_REGISTER 14 +#define IBL_INTEGRATION_SAMPLER_REGISTER 15 + +enum MaterialFlagBits +{ + MATERIAL_FLAG_BASE_COLOR_TEXTURE = (1 << 1), + MATERIAL_FLAG_METALLIC_ROUGHNESS_TEXTURE = (1 << 2), + MATERIAL_FLAG_NORMAL_TEXTURE = (1 << 3), + MATERIAL_FLAG_OCCLUSION_TEXTURE = (1 << 4), + MATERIAL_FLAG_EMISSIVE_TEXTURE = (1 << 5), +}; + +struct SceneData +{ + uint32_t IBLEnvironmentNumLevels; +}; + +struct CameraData +{ + float4x4 ViewProjectionMatrix; + float3 EyePosition; +}; + +struct DrawData +{ + uint InstanceIndex; + uint MaterialIndex; +}; + +struct InstanceData +{ + float4x4 ModelMatrix; + float4x4 NormalMatrix; +}; + +struct TextureData +{ + uint ImageIndex; + uint SamplerIndex; +}; + +struct MaterialData +{ + uint MaterialFlags; + float3 BaseColor; + float MetallicFactor; + float RoughnessFactor; + uint2 BaseColorTexture; + uint2 MetallicRoughnessTexture; + uint2 NormalTexture; + uint2 OcclusionTexture; + uint2 EmissiveTexture; + + float2 TexCoordTranslate; + float2 TexCoordScale; + float TexCoordRotate; +}; + +struct MaterialImageArray +{ + array, MAX_MATERIAL_IMAGES> Images; +}; + +struct MaterialSamplerArray +{ + array Samplers; +}; + +struct IBLIrrMapTextureArray +{ + array, MAX_IBL_TEXTURES> Textures; +}; + +struct IBLEnvMapTextureArray +{ + array, MAX_IBL_TEXTURES> Textures; +}; + +struct VSOutput +{ + float4 PositionWS; + float4 PositionCS [[position]]; + float2 TexCoord; + float3 Normal; + float4 Tangent; +}; + +struct VertexData +{ + float3 PositionOS [[attribute(0)]]; + float2 TexCoord [[attribute(1)]]; + float3 Normal [[attribute(2)]]; + float4 Tangent [[attribute(3)]]; +}; + +VSOutput vertex vsmain( + VertexData vertexData [[stage_in]], + constant DrawData& Draw [[buffer(DRAW_REGISTER)]], + constant CameraData& Camera [[buffer(CAMERA_REGISTER)]], + constant InstanceData* Instances [[buffer(INSTANCE_BUFFER_REGISTER)]]) +{ + InstanceData instance = Instances[Draw.InstanceIndex]; + + VSOutput output; + output.PositionWS = (instance.ModelMatrix * float4(vertexData.PositionOS, 1)); + output.PositionCS = (Camera.ViewProjectionMatrix * output.PositionWS); + output.TexCoord = vertexData.TexCoord; + output.Normal = vertexData.Normal; + output.Tangent = vertexData.Tangent; + return output; +} + +float3x3 CalculateTexCoordTransform(float2 translate, float rotate, float2 scale) +{ + float3x3 T = float3x3( + 1, 0, 0, 0, 1, 0, translate.x, translate.y, 1); + + float cs = cos(rotate); + float sn = sin(rotate); + float3x3 R = float3x3( + cs, sn, 0, -sn, cs, 0, 0, 0, 1); + + float3x3 S = float3x3( + scale.x, 0, 0, 0, scale.y, 0, 0, 0, 1); + + return T * R * S; +} + +float3 Fresnel_SchlickRoughness(float cosTheta, float3 F0, float roughness) +{ + float3 r = (float3)(1 - roughness); + return F0 + (max(r, F0) - F0) * pow(1 - cosTheta, 5); +} + +float Fd_Lambert() +{ + return 1.0 / PI; +} + +// circular atan2 - converts (x,y) on a unit circle to [0, 2pi] +// +#define catan2_epsilon 0.00001 +#define catan2_NAN 0.0 / 0.0 // No gaurantee this is correct + +float catan2(float y, float x) +{ + float absx = abs(x); + float absy = abs(y); + if ((absx < catan2_epsilon) && (absy < catan2_epsilon)) + { + return catan2_NAN; + } + else if ((absx > 0) && (absy == 0.0)) + { + return 0.0; + } + float s = 1.5 * 3.141592; + if (y >= 0) + { + s = 3.141592 / 2.0; + } + return s - atan(x / y); +} + +// Converts cartesian unit position 'pos' to (theta, phi) in +// spherical coodinates. +// +// theta is the azimuth angle between [0, 2pi]. +// phi is the polar angle between [0, pi]. +// +// NOTE: (0, 0, 0) will result in nan +// +float2 CartesianToSpherical(float3 pos) +{ + float absX = abs(pos.x); + float absZ = abs(pos.z); + // Handle pos pointing straight up or straight down + if ((absX < 0.00001) && (absZ <= 0.00001)) + { + // Pointing straight up + if (pos.y > 0) + { + return float2(0, 0); + } + // Pointing straight down + else if (pos.y < 0) + { + return float2(0, 3.141592); + } + // Something went terribly wrong + else + { + return float2(catan2_NAN, catan2_NAN); + } + } + float theta = catan2(pos.z, pos.x); + float phi = acos(pos.y); + return float2(theta, phi); +} + +float3 GetIBLIrradiance(texture2d IBLIrrMapTexture, sampler IBLMapSampler, float3 dir) +{ + float2 uv = CartesianToSpherical(normalize(dir)); + uv.x = saturate(uv.x / (2.0 * PI)); + uv.y = saturate(uv.y / PI); + float3 color = IBLIrrMapTexture.sample(IBLMapSampler, uv, level(0)).rgb; + return color; +} + +float3 GetIBLEnvironment(texture2d IBLEnvMapTexture, sampler IBLMapSampler, float3 dir, float lod) +{ + float2 uv = CartesianToSpherical(normalize(dir)); + uv.x = saturate(uv.x / (2.0 * PI)); + uv.y = saturate(uv.y / PI); + float3 color = IBLEnvMapTexture.sample(IBLMapSampler, uv, level(lod)).rgb; + return color; +} + +float2 GetBRDFIntegrationMap(texture2d IBLIntegrationLUT, sampler IBLIntegrationSampler, float roughness, float NoV) +{ + float2 tc = float2(saturate(roughness), saturate(NoV)); + float2 brdf = IBLIntegrationLUT.sample(IBLIntegrationSampler, tc).rg; + return brdf; +} + +/* +float2 GetBRDFIntegrationMultiscatterMap(texture2d IBLIntegrationMultiscatterLUT, sampler IBLIntegrationSampler, float roughness, float NoV) +{ + float2 tc = float2(saturate(roughness), saturate(NoV)); + float2 brdf = IBLIntegrationMultiscatterLUT.sample(IBLIntegrationSampler, tc).rg; + return brdf; +}*/ + +float4 fragment psmain( + VSOutput input [[stage_in]], + constant SceneData& Scene [[buffer(SCENE_REGISTER)]], + constant DrawData& Draw [[buffer(DRAW_REGISTER)]], + constant CameraData& Camera [[buffer(CAMERA_REGISTER)]], + constant InstanceData* Instances [[buffer(INSTANCE_BUFFER_REGISTER)]], + constant MaterialData* Materials [[buffer(MATERIAL_BUFFER_REGISTER)]], + constant MaterialImageArray* MaterialImages [[buffer(MATERIAL_IMAGES_START_REGISTER)]], + constant MaterialSamplerArray* MaterialSamplers [[buffer(MATERIAL_SAMPLER_START_REGISTER)]], + constant IBLEnvMapTextureArray* IBLEnvMap [[buffer(IBL_ENV_MAP_TEXTURE_START_REGISTER)]], + constant IBLIrrMapTextureArray* IBLIrrMap [[buffer(IBL_IRR_MAP_TEXTURE_START_REGISTER)]], + texture2d IBLIntegrationLUT [[texture(IBL_INTEGRATION_LUT_REGISTER)]], + sampler IBLMapSampler [[sampler(IBL_MAP_SAMPLER_REGISTER)]], + sampler IBLIntegrationSampler [[sampler(IBL_INTEGRATION_SAMPLER_REGISTER)]]) +{ + InstanceData instance = Instances[Draw.InstanceIndex]; + MaterialData material = Materials[Draw.MaterialIndex]; + + // Transform UV to match material + float3x3 uvTransform = CalculateTexCoordTransform(material.TexCoordTranslate, material.TexCoordRotate, material.TexCoordScale); + float2 uv = (uvTransform * float3(input.TexCoord, 1)).xy; + + // Base color + float3 baseColor = material.BaseColor; + if (material.MaterialFlags & MATERIAL_FLAG_BASE_COLOR_TEXTURE) + { + TextureData params = {material.BaseColorTexture.x, material.BaseColorTexture.y}; + + texture2d tex = MaterialImages->Images[params.ImageIndex]; + sampler samp = MaterialSamplers->Samplers[params.SamplerIndex]; + baseColor = tex.sample(samp, uv).rgb; + } + + // Metallic and roughness + // + // From GLTF spec (material.pbrMetallicRoughness.metallicRoughnessTexture): + // The metalness values are sampled from the B channel. + // The roughness values are sampled from the G channel + // + float metallic = material.MetallicFactor; + float roughness = material.RoughnessFactor; + if (material.MaterialFlags & MATERIAL_FLAG_METALLIC_ROUGHNESS_TEXTURE) + { + TextureData params = {material.MetallicRoughnessTexture.x, material.MetallicRoughnessTexture.y}; + + texture2d tex = MaterialImages->Images[params.ImageIndex]; + sampler samp = MaterialSamplers->Samplers[params.SamplerIndex]; + float3 color = tex.sample(samp, uv).rgb; + metallic = metallic * color.b; + roughness = roughness * color.g; + } + + // Normal (N) + float3 N = (instance.NormalMatrix * float4(input.Normal, 0)).xyz; + if (material.MaterialFlags & MATERIAL_FLAG_NORMAL_TEXTURE) + { + TextureData params = {material.NormalTexture.x, material.NormalTexture.y}; + + texture2d tex = MaterialImages->Images[params.ImageIndex]; + sampler samp = MaterialSamplers->Samplers[params.SamplerIndex]; + float3 vNt = tex.sample(samp, uv).rgb; + vNt = normalize((2.0 * vNt) - 1.0); + + float3 vN = N; + float3 vT = (instance.NormalMatrix * float4(input.Tangent.xyz, 0)).xyz; + float3 vB = cross(vN, vT) * input.Tangent.w; + N = normalize(vNt.x * vT + vNt.y * vB + vNt.z * vN); + } + + // Scene and geometry variables - world space + float3 P = input.PositionWS.xyz; // Position + float3 V = normalize((Camera.EyePosition - P)); // View direction + + // Specular and dieletric + float specular = 0.5; + float dielectric = 1.0 - metallic; + + // Remove gamma + baseColor = pow(baseColor, 2.2); + + // Remap + float3 diffuseColor = dielectric * baseColor; + float alpha = roughness; // * roughness; + + // Calculate F0 + float3 F0 = (0.16 * specular * specular * dielectric) + (baseColor * metallic); + + // Reflection and cosine angle between N and V + float3 R = reflect(-V, N); + float NoV = saturate(dot(N, V)); + + // Indirect lighting + float3 indirectLighting = (float3)0; + { + float3 F = Fresnel_SchlickRoughness(NoV, F0, alpha); + float3 Kd = (1 - F) * dielectric; + + float3 Rr = R; + /* + if (anisotropy != 0.0) { + Rr = GetReflectedVector(V, N, vT, vB, alpha, anisotropy); + } + */ + + // Diffuse IBL component + float3 irradiance = GetIBLIrradiance(IBLIrrMap->Textures[0], IBLMapSampler, N); + float3 Rd = irradiance * diffuseColor * Fd_Lambert(); + + // Specular IBL component + float lod = alpha * (Scene.IBLEnvironmentNumLevels - 1); + float3 prefilteredColor = GetIBLEnvironment(IBLEnvMap->Textures[0], IBLMapSampler, Rr, lod); + float2 envBRDF = (float2)0; + float3 Rs = (float3)0; + /* + if (SceneParams.Multiscatter) { + envBRDF = GetBRDFIntegrationMultiscatterMap(IBLIintegrationMultiscatterLUT, IBLIntegratoinSampler, NoV, alpha); + Rs = prefilteredColor * lerp(envBRDF.xxx, envBRDF.yyy, F0); + + float3 energyCompensation = 1.0 + F0 * (1.0 / envBRDF.y - 1.0); + Rs *= energyCompensation; + } + else + */ + { + envBRDF = GetBRDFIntegrationMap(IBLIntegrationLUT, IBLIntegrationSampler, NoV, alpha); + Rs = prefilteredColor * (F * envBRDF.x + envBRDF.y); + } + float3 Rs_dieletric = (specular * dielectric * Rs); + float3 Rs_metallic = (metallic * Rs); + float3 BRDF = Kd * Rd + (Rs_dieletric + Rs_metallic); + + indirectLighting = BRDF; + } + + // Final color + float3 finalColor = indirectLighting; + finalColor = pow(finalColor, 1 / 2.2); + + // Output + return float4(finalColor, 1); +} diff --git a/projects/common/.mtl_renderer.cpp.swp b/projects/common/.mtl_renderer.cpp.swp deleted file mode 100644 index 3775a0a2f4238851c2c7949e6d5ec9a8b941c6a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 61440 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 } -bool CalculateVertexStrides(FauxRender::Scene* pScene, std::vector& vertexStrides) -{ - assert((pScene != nullptr) && "pScene is NULL"); - - bool meshPartStrideMismatch = false; - - vertexStrides.resize(4); - - for (auto pGeometryNode : pScene->GeometryNodes) - { - assert((pGeometryNode != nullptr) && "pGeometryNode is NULL"); - assert((pGeometryNode->Type == FauxRender::SCENE_NODE_TYPE_GEOMETRY) && "node is not of drawable type"); - - const FauxRender::Mesh* pMesh = pGeometryNode->pMesh; - - for (auto& batch : pMesh->DrawBatches) - { - // Position - { - assert((batch.PositionBufferView.Format != GREX_FORMAT_UNKNOWN) && "Mesh does not contain positions!"); - uint32_t vertexStride = batch.PositionBufferView.Stride; - - meshPartStrideMismatch = meshPartStrideMismatch || - (vertexStrides[kPositionIndex] != vertexStride && vertexStrides[kPositionIndex] != 0); - - vertexStrides[kPositionIndex] = vertexStride; - } - - // Tex Coord - { - uint32_t texCoordStride = batch.NormalBufferView.Format != GREX_FORMAT_UNKNOWN - ? batch.TexCoordBufferView.Stride - : batch.PositionBufferView.Stride; // Can't have zero stride - - meshPartStrideMismatch = meshPartStrideMismatch || - (vertexStrides[kTexCoordIndex] != texCoordStride && vertexStrides[kTexCoordIndex] != 0); - - vertexStrides[kTexCoordIndex] = texCoordStride; - } - - // Normal - { - uint32_t normalStride = batch.NormalBufferView.Format != GREX_FORMAT_UNKNOWN - ? batch.NormalBufferView.Stride - : batch.PositionBufferView.Stride; // Can't have zero stride - - meshPartStrideMismatch = meshPartStrideMismatch || - (vertexStrides[kNormalIndex] != normalStride && vertexStrides[kNormalIndex] != 0); - - vertexStrides[kNormalIndex] = normalStride; - } - - // Tangent - { - uint32_t tangentStride = batch.TangentBufferView.Format != GREX_FORMAT_UNKNOWN - ? batch.TangentBufferView.Stride - : batch.PositionBufferView.Stride; // Can't have zero stride - - meshPartStrideMismatch = meshPartStrideMismatch || - (vertexStrides[kTangentIndex] != tangentStride && vertexStrides[kTangentIndex] != 0); - - vertexStrides[kTangentIndex] = tangentStride; - } - } - } - - if (meshPartStrideMismatch) - { - assert(false && "Not all mesh parts use the same vertex strides, which is not supported on Metal"); - } -} - void Draw(const FauxRender::SceneGraph* pGraph, uint32_t instanceIndex, const FauxRender::Mesh* pMesh, MTL::RenderCommandEncoder* pRenderEncoder) { assert((pMesh != nullptr) && "pMesh is NULL"); @@ -413,7 +341,7 @@ void Draw(const FauxRender::SceneGraph* pGraph, uint32_t instanceIndex, const Fa auto& batch = pMesh->DrawBatches[batchIdx]; // Skip if no material - if (IsNull(batch.pMaterial)) + if (IsNull(batch.pMaterial) || (batch.PositionBufferView.Stride != 48)) { continue; } @@ -425,14 +353,14 @@ void Draw(const FauxRender::SceneGraph* pGraph, uint32_t instanceIndex, const Fa // Position { - assert(batch.PositionBufferView.Format != GREX_FORMAT_UNKNOWN); - bufferViews[kPositionIndex] = pBuffer->Resource.Buffer.get(); - bufferOffsets[kPositionIndex] = batch.PositionBufferView.Offset; + assert(batch.PositionBufferView.Format != GREX_FORMAT_UNKNOWN); + bufferViews[kPositionIndex] = pBuffer->Resource.Buffer.get(); + bufferOffsets[kPositionIndex] = batch.PositionBufferView.Offset; } // Tex Coord { - bufferViews[kTexCoordIndex] = pBuffer->Resource.Buffer.get(); + bufferViews[kTexCoordIndex] = pBuffer->Resource.Buffer.get(); bufferOffsets[kTexCoordIndex] = batch.TexCoordBufferView.Format != GREX_FORMAT_UNKNOWN ? batch.TexCoordBufferView.Offset @@ -441,7 +369,7 @@ void Draw(const FauxRender::SceneGraph* pGraph, uint32_t instanceIndex, const Fa // Normal { - bufferViews[kNormalIndex] = pBuffer->Resource.Buffer.get(); + bufferViews[kNormalIndex] = pBuffer->Resource.Buffer.get(); bufferOffsets[kNormalIndex] = batch.NormalBufferView.Format != GREX_FORMAT_UNKNOWN ? batch.NormalBufferView.Offset @@ -450,7 +378,8 @@ void Draw(const FauxRender::SceneGraph* pGraph, uint32_t instanceIndex, const Fa // Tangent { - bufferViews[kTangentIndex] = pBuffer->Resource.Buffer.get(); + bufferViews[kTangentIndex] = pBuffer->Resource.Buffer.get(); + bufferOffsets[kTangentIndex] = batch.TangentBufferView.Format != GREX_FORMAT_UNKNOWN ? batch.TangentBufferView.Offset : batch.PositionBufferView.Offset; diff --git a/projects/common/mtl_renderer.cpp b/projects/common/mtl_renderer.cpp index 8ed2a35c..4724f525 100644 --- a/projects/common/mtl_renderer.cpp +++ b/projects/common/mtl_renderer.cpp @@ -826,8 +826,7 @@ NS::Error* CreateGraphicsPipeline2( MTL::PixelFormat rtvFormat, MTL::PixelFormat dsvFormat, MetalPipelineRenderState* pPipelineRenderState, - MetalDepthStencilState* pDepthStencilState, - uint32_t* pOptionalVertexStrides) + MetalDepthStencilState* pDepthStencilState) { MTL::VertexDescriptor* pVertexDescriptor = MTL::VertexDescriptor::alloc()->init(); if (pVertexDescriptor != nullptr) { @@ -839,13 +838,9 @@ NS::Error* CreateGraphicsPipeline2( MTL::VertexFormatFloat3, // Normal MTL::VertexFormatFloat3}; // Tangent - uint32_t strides[inputCount] = {12, 8, 12, 12}; + uint32_t strides[inputCount] = {48, 48, 48, 48}; uint32_t offsets[inputCount] = {0, 0, 0, 0}; - if (pOptionalVertexStrides) { - memcpy(strides, pOptionalVertexStrides, inputCount * sizeof(uint32_t)); - } - for (int inputIndex = 0; inputIndex < inputCount; inputIndex++) { MTL::VertexAttributeDescriptor* vertexAttribute = pVertexDescriptor->attributes()->object(inputIndex); vertexAttribute->setOffset(offsets[inputIndex]); diff --git a/projects/common/mtl_renderer.h b/projects/common/mtl_renderer.h index f38900ec..c7baa95f 100644 --- a/projects/common/mtl_renderer.h +++ b/projects/common/mtl_renderer.h @@ -134,5 +134,4 @@ NS::Error* CreateGraphicsPipeline2( MTL::PixelFormat rtvFormat, MTL::PixelFormat dsvFormat, MetalPipelineRenderState* pPipeline, - MetalDepthStencilState* pDepthStencilState, - uint32_t* pOptionalStrides); + MetalDepthStencilState* pDepthStencilState); diff --git a/projects/io/405_gltf_full_material_test_metal/405_gltf_full_material_test_metal.cpp b/projects/io/405_gltf_full_material_test_metal/405_gltf_full_material_test_metal.cpp new file mode 100644 index 00000000..6fd16204 --- /dev/null +++ b/projects/io/405_gltf_full_material_test_metal/405_gltf_full_material_test_metal.cpp @@ -0,0 +1,660 @@ +#include "window.h" + +#include "mtl_renderer.h" + +#include "mtl_faux_render.h" + +#include +#include +#include +#include +using namespace glm; + +#define CHECK_CALL(FN) \ + { \ + NS::Error* pError = FN; \ + if (pError != nullptr) \ + { \ + std::stringstream ss; \ + ss << "\n"; \ + ss << "*** FUNCTION CALL FAILED *** \n"; \ + ss << "FUNCTION: " << #FN << "\n"; \ + ss << "Error: " << pError->localizedDescription()->utf8String() << "\n"; \ + ss << "\n"; \ + GREX_LOG_ERROR(ss.str().c_str()); \ + assert(false); \ + } \ + } + +#define MAX_INSTANCES 100 +#define MAX_MATERIALS 100 +#define MAX_MATERIAL_SAMPLERS 32 +#define MAX_MATERIAL_IMAGES 1024 +#define MAX_IBL_TEXTURES 1 + +#define SCENE_REGISTER 4 +#define CAMERA_REGISTER 5 +#define DRAW_REGISTER 6 +#define INSTANCE_BUFFER_REGISTER 7 +#define MATERIAL_BUFFER_REGISTER 8 +#define MATERIAL_SAMPLER_START_REGISTER 9 +#define MATERIAL_IMAGES_START_REGISTER 10 +#define IBL_ENV_MAP_TEXTURE_START_REGISTER 11 +#define IBL_IRR_MAP_TEXTURE_START_REGISTER 12 +#define IBL_INTEGRATION_LUT_REGISTER 13 +#define IBL_MAP_SAMPLER_REGISTER 14 +#define IBL_INTEGRATION_SAMPLER_REGISTER 15 + +// ============================================================================= +// Globals +// ============================================================================= +static uint32_t gWindowWidth = 1280; +static uint32_t gWindowHeight = 720; +static bool gEnableDebug = true; + +static const uint32_t gNumIBLLUTs = 2; +static const uint32_t gNumIBLTextures = 1; +static const uint32_t gNumIBLEnvTextures = gNumIBLTextures; +static const uint32_t gNumIBLIrrTextures = gNumIBLTextures; +static const uint32_t gIBLLUTsOffset = 0; +static const uint32_t gIBLEnvTextureOffset = gIBLLUTsOffset + gNumIBLLUTs; +static const uint32_t gIBLIrrTextureOffset = gIBLEnvTextureOffset + gNumIBLEnvTextures; +static const uint32_t gMaterialTextureOffset = gIBLIrrTextureOffset + gNumIBLIrrTextures; + +static std::vector gIBLNames = {}; + +static float gTargetAngle = 0.0f; +static float gAngle = 0.0f; + +void CreateIBLTextures( + MetalRenderer* pRenderer, + MetalTexture* ppBRDFLUT, + MetalTexture* ppMultiscatterBRDFLUT, + std::vector& outIrradianceTextures, + std::vector& outEnvironmentTextures, + std::vector& outEnvNumLevels); + +void MouseMove(int x, int y, int buttons) +{ + static int prevX = x; + static int prevY = y; + + if (buttons & MOUSE_BUTTON_LEFT) + { + int dx = x - prevX; + int dy = y - prevY; + + gTargetAngle += 0.25f * dx; + } + + prevX = x; + prevY = y; +} + +// ============================================================================= +// main() +// ============================================================================= +int main(int argc, char** argv) +{ + std::unique_ptr renderer = std::make_unique(); + + if (!InitMetal(renderer.get(), gEnableDebug)) + { + return EXIT_FAILURE; + } + + // ************************************************************************* + // Compile shaders + // ************************************************************************* + std::string shaderSource = LoadString("faux_render_shaders/render_pbr_material.metal"); + + MetalShader vsShader; + MetalShader fsShader; + NS::Error* pError = nullptr; + auto library = NS::TransferPtr(renderer->Device->newLibrary( + NS::String::string(shaderSource.c_str(), NS::UTF8StringEncoding), + nullptr, + &pError)); + + if (library.get() == nullptr) + { + std::stringstream ss; + ss << "\n" + << "Shader compiler error (VS): " << pError->localizedDescription()->utf8String() << "\n"; + GREX_LOG_ERROR(ss.str().c_str()); + assert(false); + return EXIT_FAILURE; + } + + vsShader.Function = NS::TransferPtr(library->newFunction(NS::String::string("vsmain", NS::UTF8StringEncoding))); + if (vsShader.Function.get() == nullptr) + { + assert(false && "VS Shader MTL::Library::newFunction() failed"); + return EXIT_FAILURE; + } + + fsShader.Function = NS::TransferPtr(library->newFunction(NS::String::string("psmain", NS::UTF8StringEncoding))); + if (fsShader.Function.get() == nullptr) + { + assert(false && "FS Shader MTL::Library::newFunction() failed"); + return EXIT_FAILURE; + } + + // ************************************************************************* + // Scene + // ************************************************************************* + MtlFauxRender::SceneGraph graph = MtlFauxRender::SceneGraph(renderer.get()); + if (!FauxRender::LoadGLTF(GetAssetPath("scenes/material_test_001_ktx2/material_test_001.gltf"), {}, &graph)) + { + assert(false && "LoadGLTF failed"); + return EXIT_FAILURE; + } + if (!graph.InitializeResources()) + { + assert(false && "Graph resources initialization failed"); + return EXIT_FAILURE; + } + + graph.RootParameterIndices.Scene = SCENE_REGISTER; + graph.RootParameterIndices.Camera = CAMERA_REGISTER; + graph.RootParameterIndices.Draw = DRAW_REGISTER; + graph.RootParameterIndices.InstanceBuffer = INSTANCE_BUFFER_REGISTER; + graph.RootParameterIndices.MaterialBuffer = MATERIAL_BUFFER_REGISTER; + + // ************************************************************************* + // Graphics pipeline state object + // ************************************************************************* + MetalPipelineRenderState renderPipelineState; + MetalDepthStencilState depthStencilState; + CHECK_CALL(CreateGraphicsPipeline2( + renderer.get(), + &vsShader, + &fsShader, + GREX_DEFAULT_RTV_FORMAT, + GREX_DEFAULT_DSV_FORMAT, + &renderPipelineState, + &depthStencilState)); + + + // ************************************************************************* + // IBL textures + // ************************************************************************* + MetalTexture brdfLUT; + MetalTexture multiscatterBRDFLUT; + std::vector irrTextures; + std::vector envTextures; + std::vector envNumLevels; + CreateIBLTextures( + renderer.get(), + &brdfLUT, + &multiscatterBRDFLUT, + irrTextures, + envTextures, + envNumLevels); + + // ************************************************************************* + // ArgBuffers + // ************************************************************************* + MTL::Buffer* pMaterialImagesArgBuffer = nullptr; + MTL::Buffer* pMaterialSamplersArgBuffer = nullptr; + MTL::Buffer* pIrrImagesArgBuffer = nullptr; + MTL::Buffer* pEnvImagesArgBuffer = nullptr; + MTL::SamplerState* pIBLMapSamplerState = nullptr; + MTL::SamplerState* pIBLIntegrationSamplerState = nullptr; + std::vector materialImagesTextures; + std::vector materialSamplerStates; + std::vector irrImagesTextures; + std::vector envImagesTextures; + { + // Material Textures + { + MTL::ArgumentEncoder* pMaterialImagesArgEncoder = fsShader.Function->newArgumentEncoder(MATERIAL_IMAGES_START_REGISTER); + pMaterialImagesArgBuffer = renderer->Device->newBuffer( + pMaterialImagesArgEncoder->encodedLength(), + MTL::ResourceStorageModeManaged); + + pMaterialImagesArgEncoder->setArgumentBuffer(pMaterialImagesArgBuffer, 0); + + for (size_t i = 0; i < graph.Images.size(); ++i) + { + auto image = MtlFauxRender::Cast(graph.Images[i].get()); + MTL::Texture* resource = image->Resource.Texture.get(); + + pMaterialImagesArgEncoder->setTexture(resource, i); + materialImagesTextures.push_back(resource); + } + + pMaterialImagesArgBuffer->didModifyRange(NS::Range::Make(0, graph.Images.size())); + + pMaterialImagesArgEncoder->release(); + } + + // Material Samplers + { + uint32_t samplerCount = 0; + MTL::ArgumentEncoder* pMaterialSamplersArgEncoder = fsShader.Function->newArgumentEncoder(MATERIAL_SAMPLER_START_REGISTER); + pMaterialSamplersArgBuffer = renderer->Device->newBuffer( + pMaterialSamplersArgEncoder->encodedLength(), + MTL::ResourceStorageModeManaged); + + pMaterialSamplersArgEncoder->setArgumentBuffer(pMaterialSamplersArgBuffer, 0); + + // Clamped + { + MTL::SamplerDescriptor* clampedSamplerDesc = MTL::SamplerDescriptor::alloc()->init(); + + if (clampedSamplerDesc != nullptr) + { + clampedSamplerDesc->setSupportArgumentBuffers(true); + clampedSamplerDesc->setMinFilter(MTL::SamplerMinMagFilterLinear); + clampedSamplerDesc->setMagFilter(MTL::SamplerMinMagFilterLinear); + clampedSamplerDesc->setMipFilter(MTL::SamplerMipFilterLinear); + clampedSamplerDesc->setRAddressMode(MTL::SamplerAddressModeClampToEdge); + clampedSamplerDesc->setSAddressMode(MTL::SamplerAddressModeClampToEdge); + clampedSamplerDesc->setTAddressMode(MTL::SamplerAddressModeClampToEdge); + clampedSamplerDesc->setBorderColor(MTL::SamplerBorderColorOpaqueBlack); + + MTL::SamplerState* clampedSampler = renderer->Device->newSamplerState(clampedSamplerDesc); + pMaterialSamplersArgEncoder->setSamplerState(clampedSampler, samplerCount); + samplerCount++; + + clampedSamplerDesc->release(); + } + else + { + assert(false && "Clamped sampler creation failure"); + return EXIT_FAILURE; + } + } + + // Repeat + { + MTL::SamplerDescriptor* repeatSamplerDesc = MTL::SamplerDescriptor::alloc()->init(); + + if (repeatSamplerDesc != nullptr) + { + repeatSamplerDesc->setSupportArgumentBuffers(true); + repeatSamplerDesc->setMinFilter(MTL::SamplerMinMagFilterLinear); + repeatSamplerDesc->setMagFilter(MTL::SamplerMinMagFilterLinear); + repeatSamplerDesc->setMipFilter(MTL::SamplerMipFilterLinear); + repeatSamplerDesc->setRAddressMode(MTL::SamplerAddressModeRepeat); + repeatSamplerDesc->setSAddressMode(MTL::SamplerAddressModeRepeat); + repeatSamplerDesc->setTAddressMode(MTL::SamplerAddressModeRepeat); + repeatSamplerDesc->setBorderColor(MTL::SamplerBorderColorOpaqueBlack); + + MTL::SamplerState* repeatSampler = renderer->Device->newSamplerState(repeatSamplerDesc); + pMaterialSamplersArgEncoder->setSamplerState(repeatSampler, samplerCount); + samplerCount++; + + repeatSamplerDesc->release(); + } + else + { + assert(false && "Repeat sampler creation failure"); + return EXIT_FAILURE; + } + + pMaterialSamplersArgBuffer->didModifyRange(NS::Range::Make(0, samplerCount)); + } + + pMaterialSamplersArgEncoder->release(); + } + + // IBL Textures + { + // Irradiance Textures + { + MTL::ArgumentEncoder* pIrrImagesArgEncoder = + fsShader.Function->newArgumentEncoder(IBL_IRR_MAP_TEXTURE_START_REGISTER); + + pIrrImagesArgBuffer = renderer->Device->newBuffer( + pIrrImagesArgEncoder->encodedLength(), + MTL::ResourceStorageModeManaged); + + pIrrImagesArgEncoder->setArgumentBuffer(pIrrImagesArgBuffer, 0); + + for (size_t i = 0; i < irrTextures.size(); ++i) + { + MTL::Texture* irrTexture = irrTextures[i].Texture.get(); + pIrrImagesArgEncoder->setTexture(irrTexture, i); + irrImagesTextures.push_back(irrTexture); + } + + pIrrImagesArgBuffer->didModifyRange(NS::Range::Make(0, irrImagesTextures.size())); + pIrrImagesArgEncoder->release(); + } + + // Environment textures + { + MTL::ArgumentEncoder* pEnvImagesArgEncoder = + fsShader.Function->newArgumentEncoder(IBL_IRR_MAP_TEXTURE_START_REGISTER); + + pEnvImagesArgBuffer = renderer->Device->newBuffer( + pEnvImagesArgEncoder->encodedLength(), + MTL::ResourceStorageModeManaged); + + pEnvImagesArgEncoder->setArgumentBuffer(pEnvImagesArgBuffer, 0); + + for (size_t i = 0; i < envTextures.size(); ++i) + { + MTL::Texture* envTexture = envTextures[i].Texture.get(); + pEnvImagesArgEncoder->setTexture(envTexture, i); + envImagesTextures.push_back(envTexture); + } + + pEnvImagesArgBuffer->didModifyRange(NS::Range::Make(0, envImagesTextures.size())); + pEnvImagesArgEncoder->release(); + } + } + + // IBL Samplers + { + uint32_t samplerCount = 0; + + // IBL Map Sampler + { + MTL::SamplerDescriptor* iblMapSamplerDesc = MTL::SamplerDescriptor::alloc()->init(); + + if (iblMapSamplerDesc != nullptr) + { + iblMapSamplerDesc->setSupportArgumentBuffers(true); + iblMapSamplerDesc->setMinFilter(MTL::SamplerMinMagFilterLinear); + iblMapSamplerDesc->setMagFilter(MTL::SamplerMinMagFilterLinear); + iblMapSamplerDesc->setMipFilter(MTL::SamplerMipFilterLinear); + iblMapSamplerDesc->setRAddressMode(MTL::SamplerAddressModeClampToEdge); + iblMapSamplerDesc->setSAddressMode(MTL::SamplerAddressModeClampToEdge); + iblMapSamplerDesc->setTAddressMode(MTL::SamplerAddressModeClampToEdge); + iblMapSamplerDesc->setBorderColor(MTL::SamplerBorderColorOpaqueBlack); + + pIBLMapSamplerState = renderer->Device->newSamplerState(iblMapSamplerDesc); + samplerCount++; + + iblMapSamplerDesc->release(); + } + else + { + assert(false && "IBL Map sampler creation failure"); + return EXIT_FAILURE; + } + } + + // Repeat + { + MTL::SamplerDescriptor* iblIntegrationSamplerDesc = MTL::SamplerDescriptor::alloc()->init(); + + if (iblIntegrationSamplerDesc != nullptr) + { + iblIntegrationSamplerDesc->setSupportArgumentBuffers(true); + iblIntegrationSamplerDesc->setMinFilter(MTL::SamplerMinMagFilterLinear); + iblIntegrationSamplerDesc->setMagFilter(MTL::SamplerMinMagFilterLinear); + iblIntegrationSamplerDesc->setMipFilter(MTL::SamplerMipFilterLinear); + iblIntegrationSamplerDesc->setRAddressMode(MTL::SamplerAddressModeRepeat); + iblIntegrationSamplerDesc->setSAddressMode(MTL::SamplerAddressModeRepeat); + iblIntegrationSamplerDesc->setTAddressMode(MTL::SamplerAddressModeRepeat); + iblIntegrationSamplerDesc->setBorderColor(MTL::SamplerBorderColorOpaqueBlack); + + pIBLIntegrationSamplerState = renderer->Device->newSamplerState(iblIntegrationSamplerDesc); + samplerCount++; + + iblIntegrationSamplerDesc->release(); + } + else + { + assert(false && "Repeat sampler creation failure"); + return EXIT_FAILURE; + } + } + } + } + + // ************************************************************************* + // Window + // ************************************************************************* + auto window = Window::Create(gWindowWidth, gWindowHeight, "405_gltf_full_material_test_case_metal"); + if (!window) + { + assert(false && "Window::Create failed"); + return EXIT_FAILURE; + } + window->AddMouseMoveCallbacks(MouseMove); + + // ************************************************************************* + // Render Pass Description + // ************************************************************************* + MTL::RenderPassDescriptor* pRenderPassDescriptor = MTL::RenderPassDescriptor::renderPassDescriptor(); + + // ************************************************************************* + // Swapchain + // ************************************************************************* + if (!InitSwapchain(renderer.get(), window->GetNativeWindow(), window->GetWidth(), window->GetHeight(), 2, GREX_DEFAULT_DSV_FORMAT)) + { + assert(false && "InitSwapchain failed"); + return EXIT_FAILURE; + } + + // ************************************************************************* + // Main loop + // ************************************************************************* + MTL::ClearColor clearColor(0.23f, 0.23f, 0.31f, 0); + uint32_t frameIndex = 0; + + while (window->PollEvents()) + { + CA::MetalDrawable* pDrawable = renderer->pSwapchain->nextDrawable(); + assert(pDrawable != nullptr); + + uint32_t swapchainIndex = (frameIndex % renderer->SwapchainBufferCount); + frameIndex++; + + auto colorTargetDesc = NS::TransferPtr(MTL::RenderPassColorAttachmentDescriptor::alloc()->init()); + colorTargetDesc->setClearColor(clearColor); + colorTargetDesc->setTexture(pDrawable->texture()); + colorTargetDesc->setLoadAction(MTL::LoadActionClear); + colorTargetDesc->setStoreAction(MTL::StoreActionStore); + pRenderPassDescriptor->colorAttachments()->setObject(colorTargetDesc.get(), 0); + + auto depthTargetDesc = NS::TransferPtr(MTL::RenderPassDepthAttachmentDescriptor::alloc()->init()); + depthTargetDesc->setClearDepth(1); + depthTargetDesc->setTexture(renderer->SwapchainDSVBuffers[swapchainIndex].get()); + depthTargetDesc->setLoadAction(MTL::LoadActionClear); + depthTargetDesc->setStoreAction(MTL::StoreActionDontCare); + pRenderPassDescriptor->setDepthAttachment(depthTargetDesc.get()); + + MTL::CommandBuffer* pCommandBuffer = renderer->Queue->commandBuffer(); + MTL::RenderCommandEncoder* pRenderEncoder = pCommandBuffer->renderCommandEncoder(pRenderPassDescriptor); + + MTL::Viewport viewport = {0, 0, static_cast(gWindowWidth), static_cast(gWindowHeight), 0, 1}; + pRenderEncoder->setViewport(viewport); + + MTL::ScissorRect scissor = {0, 0, gWindowWidth, gWindowHeight}; + pRenderEncoder->setScissorRect(scissor); + + pRenderEncoder->setRenderPipelineState(renderPipelineState.State.get()); + pRenderEncoder->setDepthStencilState(depthStencilState.State.get()); + + pRenderEncoder->setVertexBytes(&envNumLevels[0], 16, SCENE_REGISTER); + pRenderEncoder->setFragmentBytes(&envNumLevels[0], 16, SCENE_REGISTER); + + for (size_t i = 0; i < materialImagesTextures.size(); ++i) + { + pRenderEncoder->useResource(materialImagesTextures[i], MTL::ResourceUsageRead); + } + + pRenderEncoder->setVertexBuffer(pMaterialImagesArgBuffer, 0, MATERIAL_IMAGES_START_REGISTER); + pRenderEncoder->setFragmentBuffer(pMaterialImagesArgBuffer, 0, MATERIAL_IMAGES_START_REGISTER); + + pRenderEncoder->setVertexBuffer(pMaterialSamplersArgBuffer, 0, MATERIAL_SAMPLER_START_REGISTER); + pRenderEncoder->setFragmentBuffer(pMaterialSamplersArgBuffer, 0, MATERIAL_SAMPLER_START_REGISTER); + + pRenderEncoder->setVertexTexture(brdfLUT.Texture.get(), IBL_INTEGRATION_LUT_REGISTER); + pRenderEncoder->setFragmentTexture(brdfLUT.Texture.get(), IBL_INTEGRATION_LUT_REGISTER); + + for (size_t i = 0; i < irrImagesTextures.size(); ++i) + { + pRenderEncoder->useResource(irrImagesTextures[i], MTL::ResourceUsageRead); + } + + pRenderEncoder->setVertexBuffer(pIrrImagesArgBuffer, 0, IBL_IRR_MAP_TEXTURE_START_REGISTER); + pRenderEncoder->setFragmentBuffer(pIrrImagesArgBuffer, 0, IBL_IRR_MAP_TEXTURE_START_REGISTER); + + for (size_t i = 0; i < envImagesTextures.size(); ++i) + { + pRenderEncoder->useResource(envImagesTextures[i], MTL::ResourceUsageRead); + } + + pRenderEncoder->setVertexBuffer(pEnvImagesArgBuffer, 0, IBL_ENV_MAP_TEXTURE_START_REGISTER); + pRenderEncoder->setFragmentBuffer(pEnvImagesArgBuffer, 0, IBL_ENV_MAP_TEXTURE_START_REGISTER); + + pRenderEncoder->setVertexSamplerState(pIBLMapSamplerState, IBL_MAP_SAMPLER_REGISTER); + pRenderEncoder->setFragmentSamplerState(pIBLMapSamplerState, IBL_MAP_SAMPLER_REGISTER); + + pRenderEncoder->setVertexSamplerState(pIBLIntegrationSamplerState, IBL_INTEGRATION_SAMPLER_REGISTER); + pRenderEncoder->setFragmentSamplerState(pIBLIntegrationSamplerState, IBL_INTEGRATION_SAMPLER_REGISTER); + + // Draw scene + const auto& scene = graph.Scenes[0]; + MtlFauxRender::Draw(&graph, scene.get(), pRenderEncoder); + + pRenderEncoder->endEncoding(); + + pCommandBuffer->presentDrawable(pDrawable); + pCommandBuffer->commit(); + } + + return 0; +} + +void CreateIBLTextures( + MetalRenderer* pRenderer, + MetalTexture* ppBRDFLUT, + MetalTexture* ppMultiscatterBRDFLUT, + std::vector& outIrradianceTextures, + std::vector& outEnvironmentTextures, + std::vector& outEnvNumLevels) +{ + // BRDF LUT + { + auto bitmap = LoadImage32f(GetAssetPath("IBL/brdf_lut.hdr")); + if (bitmap.Empty()) + { + assert(false && "Load image failed"); + return; + } + + CHECK_CALL(CreateTexture( + pRenderer, + bitmap.GetWidth(), + bitmap.GetHeight(), + MTL::PixelFormatRGBA32Float, + bitmap.GetSizeInBytes(), + bitmap.GetPixels(), + ppBRDFLUT)); + } + + // Multiscatter BRDF LUT + { + auto bitmap = LoadImage32f(GetAssetPath("IBL/brdf_lut_ms.hdr")); + if (bitmap.Empty()) + { + assert(false && "Load image failed"); + return; + } + + CHECK_CALL(CreateTexture( + pRenderer, + bitmap.GetWidth(), + bitmap.GetHeight(), + MTL::PixelFormatRGBA32Float, + bitmap.GetSizeInBytes(), + bitmap.GetPixels(), + ppMultiscatterBRDFLUT)); + } + + // auto iblDir = GetAssetPath("IBL"); + // std::vector iblFiles; + // for (auto& entry : std::filesystem::directory_iterator(iblDir)) + //{ + // if (!entry.is_regular_file()) + // { + // continue; + // } + // auto path = entry.path(); + // auto ext = path.extension(); + // if (ext == ".ibl") + // { + // path = std::filesystem::relative(path, iblDir.parent_path()); + // iblFiles.push_back(path); + // } + // } + + std::vector iblFiles = {GetAssetPath("IBL/machine_shop_01_4k.ibl")}; + + size_t maxEntries = std::min(gNumIBLTextures, iblFiles.size()); + for (size_t i = 0; i < maxEntries; ++i) + { + std::filesystem::path iblFile = iblFiles[i]; + + IBLMaps ibl = {}; + if (!LoadIBLMaps32f(iblFile, &ibl)) + { + GREX_LOG_ERROR("failed to load: " << iblFile); + assert(false && "IBL maps load failed failed"); + return; + } + + outEnvNumLevels.push_back(ibl.numLevels); + + // Irradiance + { + MetalTexture texture; + CHECK_CALL(CreateTexture( + pRenderer, + ibl.irradianceMap.GetWidth(), + ibl.irradianceMap.GetHeight(), + MTL::PixelFormatRGBA32Float, + ibl.irradianceMap.GetSizeInBytes(), + ibl.irradianceMap.GetPixels(), + &texture)); + outIrradianceTextures.push_back(texture); + } + + // Environment + { + const uint32_t pixelStride = ibl.environmentMap.GetPixelStride(); + const uint32_t rowStride = ibl.environmentMap.GetRowStride(); + + std::vector mipOffsets; + uint32_t levelOffset = 0; + uint32_t levelWidth = ibl.baseWidth; + uint32_t levelHeight = ibl.baseHeight; + for (uint32_t i = 0; i < ibl.numLevels; ++i) + { + MipOffset mipOffset = {}; + mipOffset.Offset = levelOffset; + mipOffset.RowStride = rowStride; + + mipOffsets.push_back(mipOffset); + + levelOffset += (rowStride * levelHeight); + levelWidth >>= 1; + levelHeight >>= 1; + } + + MetalTexture texture; + CHECK_CALL(CreateTexture( + pRenderer, + ibl.baseWidth, + ibl.baseHeight, + MTL::PixelFormatRGBA32Float, + mipOffsets, + ibl.environmentMap.GetSizeInBytes(), + ibl.environmentMap.GetPixels(), + &texture)); + outEnvironmentTextures.push_back(texture); + } + + gIBLNames.push_back(iblFile.filename().replace_extension().string()); + + GREX_LOG_INFO("Loaded " << iblFile); + } +} diff --git a/projects/io/405_gltf_full_material_test_metal/CMakeLists.txt b/projects/io/405_gltf_full_material_test_metal/CMakeLists.txt new file mode 100644 index 00000000..f4b590d7 --- /dev/null +++ b/projects/io/405_gltf_full_material_test_metal/CMakeLists.txt @@ -0,0 +1,52 @@ +cmake_minimum_required(VERSION 3.0) + +project(405_gltf_full_material_test_metal) + +add_executable( + 405_gltf_full_material_test_metal + 405_gltf_full_material_test_metal.cpp + ${GREX_PROJECTS_COMMON_DIR}/config.h + ${GREX_PROJECTS_COMMON_DIR}/mtl_renderer.h + ${GREX_PROJECTS_COMMON_DIR}/mtl_renderer.cpp + ${GREX_PROJECTS_COMMON_DIR}/mtl_renderer_utils.h + ${GREX_PROJECTS_COMMON_DIR}/mtl_renderer_utils.mm + ${GREX_PROJECTS_COMMON_DIR}/window.h + ${GREX_PROJECTS_COMMON_DIR}/window.cpp + ${GREX_PROJECTS_COMMON_DIR}/cgltf_impl.cpp + ${GREX_THIRD_PARTY_DIR}/cgltf/cgltf.h + ${GREX_PROJECTS_COMMON_DIR}/faux_render.h + ${GREX_PROJECTS_COMMON_DIR}/faux_render.cpp + ${GREX_PROJECTS_COMMON_DIR}/mtl_faux_render.h + ${GREX_PROJECTS_COMMON_DIR}/mtl_faux_render.cpp + ${GREX_PROJECTS_COMMON_DIR}/bitmap.h + ${GREX_PROJECTS_COMMON_DIR}/bitmap.cpp + ${GREX_THIRD_PARTY_DIR}/glslang/StandAlone/resource_limits_c.cpp + ${GREX_THIRD_PARTY_DIR}/glslang/StandAlone/ResourceLimits.cpp +) + +set_target_properties(405_gltf_full_material_test_metal PROPERTIES FOLDER "io") + +target_include_directories( + 405_gltf_full_material_test_metal + PUBLIC ${GREX_PROJECTS_COMMON_DIR} + ${GREX_THIRD_PARTY_DIR}/glm + ${GREX_THIRD_PARTY_DIR}/tinyobjloader + ${GREX_THIRD_PARTY_DIR}/cgltf + ${GREX_THIRD_PARTY_DIR}/stb + ${GREX_THIRD_PARTY_DIR}/MikkTSpace + ${GREX_THIRD_PARTY_DIR}/metal-cpp +) + +FIND_LIBRARY(FOUNDATION_LIBRARY Foundation) +FIND_LIBRARY(METAL_LIBRARY Metal) +FIND_LIBRARY(METALKIT_LIBRARY MetalKit) + +target_link_libraries( + 405_gltf_full_material_test_metal + PUBLIC glfw + glslang + ${FOUNDATION_LIBRARY} + ${METAL_LIBRARY} + ${METALKIT_LIBRARY} + ktx +) diff --git a/projects/io/CMakeLists.txt b/projects/io/CMakeLists.txt index 937451be..5bbe17ee 100644 --- a/projects/io/CMakeLists.txt +++ b/projects/io/CMakeLists.txt @@ -26,4 +26,5 @@ if (GREX_ENABLE_METAL) add_subdirectory(402_gltf_basic_texture_metal) add_subdirectory(403_gltf_basic_material_metal) add_subdirectory(404_gltf_basic_material_texture_metal) + add_subdirectory(405_gltf_full_material_test_metal) endif()