diff --git a/09_GeometryCreator/main.cpp b/09_GeometryCreator/main.cpp index 4ac527e08..ce0fda579 100644 --- a/09_GeometryCreator/main.cpp +++ b/09_GeometryCreator/main.cpp @@ -2,6 +2,8 @@ // This file is part of the "Nabla Engine". // For conditions of distribution and use, see copyright notice in nabla.h +#include +#include #include "common.hpp" class CSwapchainFramebuffersAndDepth final : public nbl::video::CDefaultSwapchainFramebuffers @@ -230,9 +232,9 @@ class GeometryCreatorApp final : public examples::SimpleWindowedApplication // camera { - core::vectorSIMDf cameraPosition(-5.81655884, 2.58630896, -4.23974705); - core::vectorSIMDf cameraTarget(-0.349590302, -0.213266611, 0.317821503); - matrix4SIMD projectionMatrix = matrix4SIMD::buildProjectionMatrixPerspectiveFovLH(core::radians(60.0f), float(WIN_W) / WIN_H, 0.1, 10000); + hlsl::float32_t3 cameraPosition(-5.81655884, 2.58630896, -4.23974705); + hlsl::float32_t3 cameraTarget(-0.349590302, -0.213266611, 0.317821503); + float32_t4x4 projectionMatrix = hlsl::buildProjectionMatrixPerspectiveFovLH(core::radians(60.0f), float(WIN_W) / WIN_H, 0.1, 10000); camera = Camera(cameraPosition, cameraTarget, projectionMatrix, 1.069f, 0.4f); } @@ -303,20 +305,20 @@ class GeometryCreatorApp final : public examples::SimpleWindowedApplication const auto viewMatrix = camera.getViewMatrix(); const auto viewProjectionMatrix = camera.getConcatenatedMatrix(); - core::matrix3x4SIMD modelMatrix; - modelMatrix.setTranslation(nbl::core::vectorSIMDf(0, 0, 0, 0)); - modelMatrix.setRotation(quaternion(0, 0, 0)); + hlsl::float32_t3x4 modelMatrix; + hlsl::setTranslation(modelMatrix, hlsl::float32_t3(0)); + hlsl::setRotation(modelMatrix, hlsl::quaternion::create(0, 0, 0)); - core::matrix3x4SIMD modelViewMatrix = core::concatenateBFollowedByA(viewMatrix, modelMatrix); - core::matrix4SIMD modelViewProjectionMatrix = core::concatenateBFollowedByA(viewProjectionMatrix, modelMatrix); + hlsl::float32_t3x4 modelViewMatrix = hlsl::concatenateBFollowedByA(viewMatrix, modelMatrix); + hlsl::float32_t4x4 modelViewProjectionMatrix = mul(viewProjectionMatrix, hlsl::getMatrix3x4As4x4(modelMatrix)); - core::matrix3x4SIMD normalMatrix; - modelViewMatrix.getSub3x3InverseTranspose(normalMatrix); + hlsl::float32_t3x4 normalMatrix; + //modelViewMatrix.getSub3x3InverseTranspose(normalMatrix); SBasicViewParameters uboData; - memcpy(uboData.MVP, modelViewProjectionMatrix.pointer(), sizeof(uboData.MVP)); - memcpy(uboData.MV, modelViewMatrix.pointer(), sizeof(uboData.MV)); - memcpy(uboData.NormalMat, normalMatrix.pointer(), sizeof(uboData.NormalMat)); + memcpy(uboData.MVP, &modelViewProjectionMatrix, sizeof(uboData.MVP)); + memcpy(uboData.MV, &modelViewMatrix, sizeof(uboData.MV)); + memcpy(uboData.NormalMat, &normalMatrix, sizeof(uboData.NormalMat)); { SBufferRange range; range.buffer = core::smart_refctd_ptr(resources.ubo.buffer); @@ -467,7 +469,7 @@ class GeometryCreatorApp final : public examples::SimpleWindowedApplication InputSystem::ChannelReader mouse; InputSystem::ChannelReader keyboard; - Camera camera = Camera(core::vectorSIMDf(0, 0, 0), core::vectorSIMDf(0, 0, 0), core::matrix4SIMD()); + Camera camera = Camera(hlsl::float32_t3(0, 0, 0), hlsl::float32_t3(0, 0, 0), hlsl::float32_t4x4(1)); video::CDumbPresentationOracle oracle; ResourcesBundle resources; diff --git a/22_CppCompat/Testers.h b/22_CppCompat/Testers.h new file mode 100644 index 000000000..17b2ad0db --- /dev/null +++ b/22_CppCompat/Testers.h @@ -0,0 +1,863 @@ +#ifndef _NBL_EXAMPLES_TESTS_22_CPP_COMPAT_TESTERS_INCLUDED_ +#define _NBL_EXAMPLES_TESTS_22_CPP_COMPAT_TESTERS_INCLUDED_ + +#include +#include "app_resources/common.hlsl" +#include "nbl/application_templates/MonoDeviceApplication.hpp" +#include "nbl/application_templates/MonoAssetManagerAndBuiltinResourceApplication.hpp" + +using namespace nbl; + +class ITester +{ +public: + virtual ~ITester() + { + m_outputBufferAllocation.memory->unmap(); + }; + + struct PipelineSetupData + { + std::string testShaderPath; + + core::smart_refctd_ptr device; + core::smart_refctd_ptr api; + core::smart_refctd_ptr assetMgr; + core::smart_refctd_ptr logger; + video::IPhysicalDevice* physicalDevice; + uint32_t computeFamilyIndex; + }; + + template + void setupPipeline(const PipelineSetupData& pipleineSetupData) + { + // setting up pipeline in the constructor + m_device = core::smart_refctd_ptr(pipleineSetupData.device); + m_physicalDevice = pipleineSetupData.physicalDevice; + m_api = core::smart_refctd_ptr(pipleineSetupData.api); + m_assetMgr = core::smart_refctd_ptr(pipleineSetupData.assetMgr); + m_logger = core::smart_refctd_ptr(pipleineSetupData.logger); + m_queueFamily = pipleineSetupData.computeFamilyIndex; + m_semaphoreCounter = 0; + m_semaphore = m_device->createSemaphore(0); + m_cmdpool = m_device->createCommandPool(m_queueFamily, video::IGPUCommandPool::CREATE_FLAGS::RESET_COMMAND_BUFFER_BIT); + if (!m_cmdpool->createCommandBuffers(video::IGPUCommandPool::BUFFER_LEVEL::PRIMARY, 1u, &m_cmdbuf)) + logFail("Failed to create Command Buffers!\n"); + + // Load shaders, set up pipeline + core::smart_refctd_ptr shader; + { + asset::IAssetLoader::SAssetLoadParams lp = {}; + lp.logger = m_logger.get(); + lp.workingDirectory = ""; // virtual root + auto assetBundle = m_assetMgr->getAsset(pipleineSetupData.testShaderPath, lp); + const auto assets = assetBundle.getContents(); + if (assets.empty()) + { + logFail("Could not load shader!"); + assert(0); + } + + // It would be super weird if loading a shader from a file produced more than 1 asset + assert(assets.size() == 1); + core::smart_refctd_ptr source = asset::IAsset::castDown(assets[0]); + + auto* compilerSet = m_assetMgr->getCompilerSet(); + + asset::IShaderCompiler::SCompilerOptions options = {}; + options.stage = source->getStage(); + options.targetSpirvVersion = m_device->getPhysicalDevice()->getLimits().spirvVersion; + options.spirvOptimizer = nullptr; + options.debugInfoFlags |= asset::IShaderCompiler::E_DEBUG_INFO_FLAGS::EDIF_SOURCE_BIT; + options.preprocessorOptions.sourceIdentifier = source->getFilepathHint(); + options.preprocessorOptions.logger = m_logger.get(); + options.preprocessorOptions.includeFinder = compilerSet->getShaderCompiler(source->getContentType())->getDefaultIncludeFinder(); + + auto spirv = compilerSet->compileToSPIRV(source.get(), options); + + video::ILogicalDevice::SShaderCreationParameters params{}; + params.cpushader = spirv.get(); + shader = m_device->createShader(params); + } + + if (!shader) + logFail("Failed to create a GPU Shader, seems the Driver doesn't like the SPIR-V we're feeding it!\n"); + + video::IGPUDescriptorSetLayout::SBinding bindings[2] = { + { + .binding = 0, + .type = asset::IDescriptor::E_TYPE::ET_STORAGE_BUFFER, + .createFlags = video::IGPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS::ECF_NONE, + .stageFlags = ShaderStage::ESS_COMPUTE, + .count = 1 + }, + { + .binding = 1, + .type = asset::IDescriptor::E_TYPE::ET_STORAGE_BUFFER, + .createFlags = video::IGPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS::ECF_NONE, + .stageFlags = ShaderStage::ESS_COMPUTE, + .count = 1 + } + }; + + core::smart_refctd_ptr dsLayout = m_device->createDescriptorSetLayout(bindings); + if (!dsLayout) + logFail("Failed to create a Descriptor Layout!\n"); + + m_pplnLayout = m_device->createPipelineLayout({}, core::smart_refctd_ptr(dsLayout)); + if (!m_pplnLayout) + logFail("Failed to create a Pipeline Layout!\n"); + + { + video::IGPUComputePipeline::SCreationParams params = {}; + params.layout = m_pplnLayout.get(); + params.shader.entryPoint = "main"; + params.shader.shader = shader.get(); + if (!m_device->createComputePipelines(nullptr, { ¶ms,1 }, &m_pipeline)) + logFail("Failed to create pipelines (compile & link shaders)!\n"); + } + + // Allocate memory of the input buffer + { + constexpr size_t BufferSize = sizeof(InputStruct); + + video::IGPUBuffer::SCreationParams params = {}; + params.size = BufferSize; + params.usage = video::IGPUBuffer::EUF_STORAGE_BUFFER_BIT; + core::smart_refctd_ptr inputBuff = m_device->createBuffer(std::move(params)); + if (!inputBuff) + logFail("Failed to create a GPU Buffer of size %d!\n", params.size); + + inputBuff->setObjectDebugName("emulated_float64_t output buffer"); + + video::IDeviceMemoryBacked::SDeviceMemoryRequirements reqs = inputBuff->getMemoryReqs(); + reqs.memoryTypeBits &= m_physicalDevice->getHostVisibleMemoryTypeBits(); + + m_inputBufferAllocation = m_device->allocate(reqs, inputBuff.get(), video::IDeviceMemoryAllocation::EMAF_NONE); + if (!m_inputBufferAllocation.isValid()) + logFail("Failed to allocate Device Memory compatible with our GPU Buffer!\n"); + + assert(inputBuff->getBoundMemory().memory == m_inputBufferAllocation.memory.get()); + core::smart_refctd_ptr pool = m_device->createDescriptorPoolForDSLayouts(video::IDescriptorPool::ECF_NONE, { &dsLayout.get(),1 }); + + m_ds = pool->createDescriptorSet(core::smart_refctd_ptr(dsLayout)); + { + video::IGPUDescriptorSet::SDescriptorInfo info[1]; + info[0].desc = core::smart_refctd_ptr(inputBuff); + info[0].info.buffer = { .offset = 0,.size = BufferSize }; + video::IGPUDescriptorSet::SWriteDescriptorSet writes[1] = { + {.dstSet = m_ds.get(),.binding = 0,.arrayElement = 0,.count = 1,.info = info} + }; + m_device->updateDescriptorSets(writes, {}); + } + } + + // Allocate memory of the output buffer + { + constexpr size_t BufferSize = sizeof(OutputStruct); + + video::IGPUBuffer::SCreationParams params = {}; + params.size = BufferSize; + params.usage = video::IGPUBuffer::EUF_STORAGE_BUFFER_BIT; + core::smart_refctd_ptr outputBuff = m_device->createBuffer(std::move(params)); + if (!outputBuff) + logFail("Failed to create a GPU Buffer of size %d!\n", params.size); + + outputBuff->setObjectDebugName("emulated_float64_t output buffer"); + + video::IDeviceMemoryBacked::SDeviceMemoryRequirements reqs = outputBuff->getMemoryReqs(); + reqs.memoryTypeBits &= m_physicalDevice->getHostVisibleMemoryTypeBits(); + + m_outputBufferAllocation = m_device->allocate(reqs, outputBuff.get(), video::IDeviceMemoryAllocation::EMAF_NONE); + if (!m_outputBufferAllocation.isValid()) + logFail("Failed to allocate Device Memory compatible with our GPU Buffer!\n"); + + assert(outputBuff->getBoundMemory().memory == m_outputBufferAllocation.memory.get()); + core::smart_refctd_ptr pool = m_device->createDescriptorPoolForDSLayouts(video::IDescriptorPool::ECF_NONE, { &dsLayout.get(),1 }); + + { + video::IGPUDescriptorSet::SDescriptorInfo info[1]; + info[0].desc = core::smart_refctd_ptr(outputBuff); + info[0].info.buffer = { .offset = 0,.size = BufferSize }; + video::IGPUDescriptorSet::SWriteDescriptorSet writes[1] = { + {.dstSet = m_ds.get(),.binding = 1,.arrayElement = 0,.count = 1,.info = info} + }; + m_device->updateDescriptorSets(writes, {}); + } + } + + if (!m_outputBufferAllocation.memory->map({ 0ull,m_outputBufferAllocation.memory->getAllocationSize() }, video::IDeviceMemoryAllocation::EMCAF_READ)) + logFail("Failed to map the Device Memory!\n"); + + // if the mapping is not coherent the range needs to be invalidated to pull in new data for the CPU's caches + const video::ILogicalDevice::MappedMemoryRange memoryRange(m_outputBufferAllocation.memory.get(), 0ull, m_outputBufferAllocation.memory->getAllocationSize()); + if (!m_outputBufferAllocation.memory->getMemoryPropertyFlags().hasFlags(video::IDeviceMemoryAllocation::EMPF_HOST_COHERENT_BIT)) + m_device->invalidateMappedMemoryRanges(1, &memoryRange); + + assert(memoryRange.valid() && memoryRange.length >= sizeof(OutputStruct)); + + m_queue = m_device->getQueue(m_queueFamily, 0); + } + + enum class TestType + { + CPU, + GPU + }; + + template + void verifyTestValue(const std::string& memberName, const T& expectedVal, const T& testVal, const TestType testType) + { + static constexpr float MaxAllowedError = 0.1f; + if (std::abs(double(expectedVal) - double(testVal)) <= MaxAllowedError) + return; + + std::stringstream ss; + switch (testType) + { + case TestType::CPU: + ss << "CPU TEST ERROR:\n"; + case TestType::GPU: + ss << "GPU TEST ERROR:\n"; + } + + ss << "nbl::hlsl::" << memberName << " produced incorrect output! test value: " << testVal << " expected value: " << expectedVal << '\n'; + + m_logger->log(ss.str().c_str(), system::ILogger::ELL_ERROR); + } + + template + void verifyTestVector3dValue(const std::string& memberName, const nbl::hlsl::vector& expectedVal, const nbl::hlsl::vector& testVal, const TestType testType) + { + static constexpr float MaxAllowedError = 0.1f; + if (std::abs(double(expectedVal.x) - double(testVal.x)) <= MaxAllowedError && + std::abs(double(expectedVal.y) - double(testVal.y)) <= MaxAllowedError && + std::abs(double(expectedVal.z) - double(testVal.z)) <= MaxAllowedError) + return; + + std::stringstream ss; + switch (testType) + { + case TestType::CPU: + ss << "CPU TEST ERROR:\n"; + case TestType::GPU: + ss << "GPU TEST ERROR:\n"; + } + + ss << "nbl::hlsl::" << memberName << " produced incorrect output! test value: " << + testVal.x << ' ' << testVal.y << ' ' << testVal.z << + " expected value: " << expectedVal.x << ' ' << expectedVal.y << ' ' << expectedVal.z << '\n'; + + m_logger->log(ss.str().c_str(), system::ILogger::ELL_ERROR); + } + + template + void verifyTestMatrix3x3Value(const std::string& memberName, const nbl::hlsl::matrix& expectedVal, const nbl::hlsl::matrix& testVal, const TestType testType) + { + for (int i = 0; i < 3; ++i) + { + auto expectedValRow = expectedVal[i]; + auto testValRow = testVal[i]; + verifyTestVector3dValue(memberName, expectedValRow, testValRow, testType); + } + } + +protected: + uint32_t m_queueFamily; + core::smart_refctd_ptr m_device; + core::smart_refctd_ptr m_api; + video::IPhysicalDevice* m_physicalDevice; + core::smart_refctd_ptr m_assetMgr; + core::smart_refctd_ptr m_logger; + video::IDeviceMemoryAllocator::SAllocation m_inputBufferAllocation = {}; + video::IDeviceMemoryAllocator::SAllocation m_outputBufferAllocation = {}; + core::smart_refctd_ptr m_cmdbuf = nullptr; + core::smart_refctd_ptr m_cmdpool = nullptr; + core::smart_refctd_ptr m_ds = nullptr; + core::smart_refctd_ptr m_pplnLayout = nullptr; + core::smart_refctd_ptr m_pipeline; + core::smart_refctd_ptr m_semaphore; + video::IQueue* m_queue; + uint64_t m_semaphoreCounter; + + template + OutputStruct dispatch(const InputStruct& input) + { + // Update input buffer + if (!m_inputBufferAllocation.memory->map({ 0ull,m_inputBufferAllocation.memory->getAllocationSize() }, video::IDeviceMemoryAllocation::EMCAF_READ)) + logFail("Failed to map the Device Memory!\n"); + + const video::ILogicalDevice::MappedMemoryRange memoryRange(m_inputBufferAllocation.memory.get(), 0ull, m_inputBufferAllocation.memory->getAllocationSize()); + if (!m_inputBufferAllocation.memory->getMemoryPropertyFlags().hasFlags(video::IDeviceMemoryAllocation::EMPF_HOST_COHERENT_BIT)) + m_device->invalidateMappedMemoryRanges(1, &memoryRange); + + std::memcpy(static_cast(m_inputBufferAllocation.memory->getMappedPointer()), &input, sizeof(InputStruct)); + + m_inputBufferAllocation.memory->unmap(); + + // record command buffer + m_cmdbuf->reset(video::IGPUCommandBuffer::RESET_FLAGS::NONE); + m_cmdbuf->begin(video::IGPUCommandBuffer::USAGE::NONE); + m_cmdbuf->beginDebugMarker("test", core::vector4df_SIMD(0, 1, 0, 1)); + m_cmdbuf->bindComputePipeline(m_pipeline.get()); + m_cmdbuf->bindDescriptorSets(nbl::asset::EPBP_COMPUTE, m_pplnLayout.get(), 0, 1, &m_ds.get()); + m_cmdbuf->dispatch(16, 1, 1); + m_cmdbuf->endDebugMarker(); + m_cmdbuf->end(); + + video::IQueue::SSubmitInfo submitInfos[1] = {}; + const video::IQueue::SSubmitInfo::SCommandBufferInfo cmdbufs[] = { {.cmdbuf = m_cmdbuf.get()} }; + submitInfos[0].commandBuffers = cmdbufs; + const video::IQueue::SSubmitInfo::SSemaphoreInfo signals[] = { {.semaphore = m_semaphore.get(), .value = ++m_semaphoreCounter, .stageMask = asset::PIPELINE_STAGE_FLAGS::COMPUTE_SHADER_BIT} }; + submitInfos[0].signalSemaphores = signals; + + m_api->startCapture(); + m_queue->submit(submitInfos); + m_api->endCapture(); + + m_device->waitIdle(); + OutputStruct output; + std::memcpy(&output, static_cast(m_outputBufferAllocation.memory->getMappedPointer()), sizeof(OutputStruct)); + m_device->waitIdle(); + + return output; + } + +private: + template + inline void logFail(const char* msg, Args&&... args) + { + m_logger->log(msg, system::ILogger::ELL_ERROR, std::forward(args)...); + exit(-1); + } +}; + +class CTgmathTester final : public ITester +{ +public: + void performTests() + { + std::random_device rd; + std::mt19937 mt(rd()); + + std::uniform_real_distribution realDistributionNeg(-50.0f, -1.0f); + std::uniform_real_distribution realDistributionPos(1.0f, 50.0f); + std::uniform_real_distribution realDistribution(-100.0f, 100.0f); + std::uniform_real_distribution realDistributionSmall(1.0f, 4.0f); + std::uniform_int_distribution intDistribution(-100, 100); + std::uniform_int_distribution coinFlipDistribution(0, 1); + + m_logger->log("tgmath.hlsl TESTS:", system::ILogger::ELL_PERFORMANCE); + for (int i = 0; i < Iterations; ++i) + { + // Set input thest values that will be used in both CPU and GPU tests + TgmathIntputTestValues testInput; + testInput.floor = realDistribution(mt); + testInput.isnan = coinFlipDistribution(mt) ? realDistribution(mt) : std::numeric_limits::quiet_NaN(); + testInput.isinf = coinFlipDistribution(mt) ? realDistribution(mt) : std::numeric_limits::infinity(); + testInput.powX = realDistributionSmall(mt); + testInput.powY = realDistributionSmall(mt); + testInput.exp = realDistributionSmall(mt); + testInput.exp2 = realDistributionSmall(mt); + testInput.log = realDistribution(mt); + testInput.log2 = realDistribution(mt); + testInput.absF = realDistribution(mt); + testInput.absI = intDistribution(mt); + testInput.sqrt = realDistribution(mt); + testInput.sin = realDistribution(mt); + testInput.cos = realDistribution(mt); + testInput.acos = realDistribution(mt); + testInput.modf = realDistribution(mt); + testInput.round = realDistribution(mt); + testInput.roundEven = coinFlipDistribution(mt) ? realDistributionSmall(mt) : (static_cast(intDistribution(mt) / 2) + 0.5f); + testInput.trunc = realDistribution(mt); + testInput.ceil = realDistribution(mt); + testInput.fmaX = realDistribution(mt); + testInput.fmaY = realDistribution(mt); + testInput.fmaZ = realDistribution(mt); + testInput.ldexpArg = realDistributionSmall(mt); + testInput.ldexpExp = intDistribution(mt); + + testInput.floorVec = float32_t3(realDistribution(mt), realDistribution(mt), realDistribution(mt)); + testInput.isnanVec = float32_t3(realDistribution(mt), realDistribution(mt), realDistribution(mt)); + testInput.isinfVec = float32_t3(realDistribution(mt), realDistribution(mt), realDistribution(mt)); + testInput.powXVec = float32_t3(realDistributionSmall(mt), realDistributionSmall(mt), realDistributionSmall(mt)); + testInput.powYVec = float32_t3(realDistributionSmall(mt), realDistributionSmall(mt), realDistributionSmall(mt)); + testInput.expVec = float32_t3(realDistributionSmall(mt), realDistributionSmall(mt), realDistributionSmall(mt)); + testInput.exp2Vec = float32_t3(realDistributionSmall(mt), realDistributionSmall(mt), realDistributionSmall(mt)); + testInput.logVec = float32_t3(realDistribution(mt), realDistribution(mt), realDistribution(mt)); + testInput.log2Vec = float32_t3(realDistribution(mt), realDistribution(mt), realDistribution(mt)); + testInput.absFVec = float32_t3(realDistribution(mt), realDistribution(mt), realDistribution(mt)); + testInput.absIVec = int32_t3(intDistribution(mt), intDistribution(mt), intDistribution(mt)); + testInput.sqrtVec = float32_t3(realDistribution(mt), realDistribution(mt), realDistribution(mt)); + testInput.sinVec = float32_t3(realDistribution(mt), realDistribution(mt), realDistribution(mt)); + testInput.cosVec = float32_t3(realDistribution(mt), realDistribution(mt), realDistribution(mt)); + testInput.acosVec = float32_t3(realDistribution(mt), realDistribution(mt), realDistribution(mt)); + testInput.modfVec = float32_t3(realDistribution(mt), realDistribution(mt), realDistribution(mt)); + testInput.ldexpArgVec = float32_t3(realDistributionSmall(mt), realDistributionSmall(mt), realDistributionSmall(mt)); + testInput.ldexpExpVec = float32_t3(intDistribution(mt), intDistribution(mt), intDistribution(mt)); + + testInput.modfStruct = realDistribution(mt); + testInput.modfStructVec = float32_t3(realDistribution(mt), realDistribution(mt), realDistribution(mt)); + testInput.frexpStruct = realDistribution(mt); + testInput.frexpStructVec = float32_t3(realDistribution(mt), realDistribution(mt), realDistribution(mt)); + + // use std library functions to determine expected test values, the output of functions from tgmath.hlsl will be verified against these values + TgmathTestValues expected; + expected.floor = std::floor(testInput.floor); + expected.isnan = std::isnan(testInput.isnan); + expected.isinf = std::isinf(testInput.isinf); + expected.pow = std::pow(testInput.powX, testInput.powY); + expected.exp = std::exp(testInput.exp); + expected.exp2 = std::exp2(testInput.exp2); + expected.log = std::log(testInput.log); + expected.log2 = std::log2(testInput.log2); + expected.absF = std::abs(testInput.absF); + expected.absI = std::abs(testInput.absI); + expected.sqrt = std::sqrt(testInput.sqrt); + expected.sin = std::sin(testInput.sin); + expected.cos = std::cos(testInput.cos); + expected.acos = std::acos(testInput.acos); + { + float tmp; + expected.modf = std::modf(testInput.modf, &tmp); + } + expected.round = std::round(testInput.round); + // TODO: uncomment when C++23 + //expected.roundEven = std::roundeven(testInput.roundEven); + // TODO: remove when C++23 + auto roundeven = [](const float& val) -> float + { + float tmp; + if (std::abs(std::modf(val, &tmp)) == 0.5f) + { + int32_t result = static_cast(val); + if (result % 2 != 0) + result >= 0 ? ++result : --result; + return result; + } + + return std::round(val); + }; + expected.roundEven = roundeven(testInput.roundEven); + + expected.trunc = std::trunc(testInput.trunc); + expected.ceil = std::ceil(testInput.ceil); + expected.fma = std::fma(testInput.fmaX, testInput.fmaY, testInput.fmaZ); + expected.ldexp = std::ldexp(testInput.ldexpArg, testInput.ldexpExp); + + expected.floorVec = float32_t3(std::floor(testInput.floorVec.x), std::floor(testInput.floorVec.y), std::floor(testInput.floorVec.z)); + + expected.isnanVec = float32_t3(std::isnan(testInput.isnanVec.x), std::isnan(testInput.isnanVec.y), std::isnan(testInput.isnanVec.z)); + expected.isinfVec = float32_t3(std::isinf(testInput.isinfVec.x), std::isinf(testInput.isinfVec.y), std::isinf(testInput.isinfVec.z)); + + expected.powVec.x = std::pow(testInput.powXVec.x, testInput.powYVec.x); + expected.powVec.y = std::pow(testInput.powXVec.y, testInput.powYVec.y); + expected.powVec.z = std::pow(testInput.powXVec.z, testInput.powYVec.z); + + expected.expVec = float32_t3(std::exp(testInput.expVec.x), std::exp(testInput.expVec.y), std::exp(testInput.expVec.z)); + expected.exp2Vec = float32_t3(std::exp2(testInput.exp2Vec.x), std::exp2(testInput.exp2Vec.y), std::exp2(testInput.exp2Vec.z)); + expected.logVec = float32_t3(std::log(testInput.logVec.x), std::log(testInput.logVec.y), std::log(testInput.logVec.z)); + expected.log2Vec = float32_t3(std::log2(testInput.log2Vec.x), std::log2(testInput.log2Vec.y), std::log2(testInput.log2Vec.z)); + expected.absFVec = float32_t3(std::abs(testInput.absFVec.x), std::abs(testInput.absFVec.y), std::abs(testInput.absFVec.z)); + expected.absIVec = float32_t3(std::abs(testInput.absIVec.x), std::abs(testInput.absIVec.y), std::abs(testInput.absIVec.z)); + expected.sqrtVec = float32_t3(std::sqrt(testInput.sqrtVec.x), std::sqrt(testInput.sqrtVec.y), std::sqrt(testInput.sqrtVec.z)); + expected.cosVec = float32_t3(std::cos(testInput.cosVec.x), std::cos(testInput.cosVec.y), std::cos(testInput.cosVec.z)); + expected.sinVec = float32_t3(std::sin(testInput.sinVec.x), std::sin(testInput.sinVec.y), std::sin(testInput.sinVec.z)); + expected.acosVec = float32_t3(std::acos(testInput.acosVec.x), std::acos(testInput.acosVec.y), std::acos(testInput.acosVec.z)); + { + float tmp; + expected.modfVec = float32_t3(std::modf(testInput.modfVec.x, &tmp), std::modf(testInput.modfVec.y, &tmp), std::modf(testInput.modfVec.z, &tmp)); + } + expected.roundVec = float32_t3( + std::round(testInput.roundVec.x), + std::round(testInput.roundVec.y), + std::round(testInput.roundVec.z) + ); + // TODO: uncomment when C++23 + //expected.roundEven = float32_t( + // std::roundeven(testInput.roundEvenVec.x), + // std::roundeven(testInput.roundEvenVec.y), + // std::roundeven(testInput.roundEvenVec.z) + // ); + // TODO: remove when C++23 + expected.roundEvenVec = float32_t3( + roundeven(testInput.roundEvenVec.x), + roundeven(testInput.roundEvenVec.y), + roundeven(testInput.roundEvenVec.z) + ); + + expected.truncVec = float32_t3(std::trunc(testInput.truncVec.x), std::trunc(testInput.truncVec.y), std::trunc(testInput.truncVec.z)); + expected.ceilVec = float32_t3(std::ceil(testInput.ceilVec.x), std::ceil(testInput.ceilVec.y), std::ceil(testInput.ceilVec.z)); + expected.fmaVec = float32_t3( + std::fma(testInput.fmaXVec.x, testInput.fmaYVec.x, testInput.fmaZVec.x), + std::fma(testInput.fmaXVec.y, testInput.fmaYVec.y, testInput.fmaZVec.y), + std::fma(testInput.fmaXVec.z, testInput.fmaYVec.z, testInput.fmaZVec.z) + ); + expected.ldexpVec = float32_t3( + std::ldexp(testInput.ldexpArgVec.x, testInput.ldexpExpVec.x), + std::ldexp(testInput.ldexpArgVec.y, testInput.ldexpExpVec.y), + std::ldexp(testInput.ldexpArgVec.z, testInput.ldexpExpVec.z) + ); + + { + ModfOutput expectedModfStructOutput; + expectedModfStructOutput.fractionalPart = std::modf(testInput.modfStruct, &expectedModfStructOutput.wholeNumberPart); + expected.modfStruct = expectedModfStructOutput; + + ModfOutput expectedModfStructOutputVec; + for (int i = 0; i < 3; ++i) + expectedModfStructOutputVec.fractionalPart[i] = std::modf(testInput.modfStructVec[i], &expectedModfStructOutputVec.wholeNumberPart[i]); + expected.modfStructVec = expectedModfStructOutputVec; + } + + { + FrexpOutput expectedFrexpStructOutput; + expectedFrexpStructOutput.significand = std::frexp(testInput.frexpStruct, &expectedFrexpStructOutput.exponent); + expected.frexpStruct = expectedFrexpStructOutput; + + FrexpOutput expectedFrexpStructOutputVec; + for (int i = 0; i < 3; ++i) + expectedFrexpStructOutputVec.significand[i] = std::frexp(testInput.frexpStructVec[i], &expectedFrexpStructOutputVec.exponent[i]); + expected.frexpStructVec = expectedFrexpStructOutputVec; + } + + performCpuTests(testInput, expected); + performGpuTests(testInput, expected); + } + m_logger->log("tgmath.hlsl TESTS DONE.", system::ILogger::ELL_PERFORMANCE); + } + +private: + inline static constexpr int Iterations = 1u; + + void performCpuTests(const TgmathIntputTestValues& commonTestInputValues, const TgmathTestValues& expectedTestValues) + { + TgmathTestValues cpuTestValues; + cpuTestValues.fillTestValues(commonTestInputValues); + verifyTestValues(expectedTestValues, cpuTestValues, ITester::TestType::CPU); + + } + + void performGpuTests(const TgmathIntputTestValues& commonTestInputValues, const TgmathTestValues& expectedTestValues) + { + TgmathTestValues gpuTestValues; + gpuTestValues = dispatch(commonTestInputValues); + verifyTestValues(expectedTestValues, gpuTestValues, ITester::TestType::GPU); + } + + void verifyTestValues(const TgmathTestValues& expectedTestValues, const TgmathTestValues& testValues, ITester::TestType testType) + { + verifyTestValue("floor", expectedTestValues.floor, testValues.floor, testType); + verifyTestValue("isnan", expectedTestValues.isnan, testValues.isnan, testType); + verifyTestValue("isinf", expectedTestValues.isinf, testValues.isinf, testType); + verifyTestValue("pow", expectedTestValues.pow, testValues.pow, testType); + verifyTestValue("exp", expectedTestValues.exp, testValues.exp, testType); + verifyTestValue("exp2", expectedTestValues.exp2, testValues.exp2, testType); + verifyTestValue("log", expectedTestValues.log, testValues.log, testType); + verifyTestValue("log2", expectedTestValues.log2, testValues.log2, testType); + verifyTestValue("absF", expectedTestValues.absF, testValues.absF, testType); + verifyTestValue("absI", expectedTestValues.absI, testValues.absI, testType); + verifyTestValue("sqrt", expectedTestValues.sqrt, testValues.sqrt, testType); + verifyTestValue("sin", expectedTestValues.sin, testValues.sin, testType); + verifyTestValue("cos", expectedTestValues.cos, testValues.cos, testType); + verifyTestValue("acos", expectedTestValues.acos, testValues.acos, testType); + verifyTestValue("modf", expectedTestValues.modf, testValues.modf, testType); + verifyTestValue("round", expectedTestValues.round, testValues.round, testType); + verifyTestValue("roundEven", expectedTestValues.roundEven, testValues.roundEven, testType); + verifyTestValue("trunc", expectedTestValues.trunc, testValues.trunc, testType); + verifyTestValue("ceil", expectedTestValues.ceil, testValues.ceil, testType); + verifyTestValue("fma", expectedTestValues.fma, testValues.fma, testType); + verifyTestValue("ldexp", expectedTestValues.ldexp, testValues.ldexp, testType); + + verifyTestVector3dValue("floorVec", expectedTestValues.floorVec, testValues.floorVec, testType); + verifyTestVector3dValue("isnanVec", expectedTestValues.isnanVec, testValues.isnanVec, testType); + verifyTestVector3dValue("isinfVec", expectedTestValues.isinfVec, testValues.isinfVec, testType); + verifyTestVector3dValue("powVec", expectedTestValues.powVec, testValues.powVec, testType); + verifyTestVector3dValue("expVec", expectedTestValues.expVec, testValues.expVec, testType); + verifyTestVector3dValue("exp2Vec", expectedTestValues.exp2Vec, testValues.exp2Vec, testType); + verifyTestVector3dValue("logVec", expectedTestValues.logVec, testValues.logVec, testType); + verifyTestVector3dValue("log2Vec", expectedTestValues.log2Vec, testValues.log2Vec, testType); + verifyTestVector3dValue("absFVec", expectedTestValues.absFVec, testValues.absFVec, testType); + verifyTestVector3dValue("absIVec", expectedTestValues.absIVec, testValues.absIVec, testType); + verifyTestVector3dValue("sqrtVec", expectedTestValues.sqrtVec, testValues.sqrtVec, testType); + verifyTestVector3dValue("sinVec", expectedTestValues.sinVec, testValues.sinVec, testType); + verifyTestVector3dValue("cosVec", expectedTestValues.cosVec, testValues.cosVec, testType); + verifyTestVector3dValue("acosVec", expectedTestValues.acosVec, testValues.acosVec, testType); + verifyTestVector3dValue("modfVec", expectedTestValues.modfVec, testValues.modfVec, testType); + verifyTestVector3dValue("roundVec", expectedTestValues.roundVec, testValues.roundVec, testType); + verifyTestVector3dValue("roundEvenVec", expectedTestValues.roundEvenVec, testValues.roundEvenVec, testType); + verifyTestVector3dValue("truncVec", expectedTestValues.truncVec, testValues.truncVec, testType); + verifyTestVector3dValue("ceilVec", expectedTestValues.ceilVec, testValues.ceilVec, testType); + verifyTestVector3dValue("fmaVec", expectedTestValues.fmaVec, testValues.fmaVec, testType); + verifyTestVector3dValue("ldexp", expectedTestValues.ldexpVec, testValues.ldexpVec, testType); + + // verify output of struct producing functions + verifyTestValue("modfStruct", expectedTestValues.modfStruct.fractionalPart, testValues.modfStruct.fractionalPart, testType); + verifyTestValue("modfStruct", expectedTestValues.modfStruct.wholeNumberPart, testValues.modfStruct.wholeNumberPart, testType); + verifyTestVector3dValue("modfStructVec", expectedTestValues.modfStructVec.fractionalPart, testValues.modfStructVec.fractionalPart, testType); + verifyTestVector3dValue("modfStructVec", expectedTestValues.modfStructVec.wholeNumberPart, testValues.modfStructVec.wholeNumberPart, testType); + + verifyTestValue("frexpStruct", expectedTestValues.frexpStruct.significand, testValues.frexpStruct.significand, testType); + verifyTestValue("frexpStruct", expectedTestValues.frexpStruct.exponent, testValues.frexpStruct.exponent, testType); + verifyTestVector3dValue("frexpStructVec", expectedTestValues.frexpStructVec.significand, testValues.frexpStructVec.significand, testType); + verifyTestVector3dValue("frexpStructVec", expectedTestValues.frexpStructVec.exponent, testValues.frexpStructVec.exponent, testType); + } +}; + +class CIntrinsicsTester final : public ITester +{ +public: + void performTests() + { + std::random_device rd; + std::mt19937 mt(rd()); + + std::uniform_real_distribution realDistributionNeg(-50.0f, -1.0f); + std::uniform_real_distribution realDistributionPos(1.0f, 50.0f); + std::uniform_real_distribution realDistributionZeroToOne(0.0f, 1.0f); + std::uniform_real_distribution realDistribution(-100.0f, 100.0f); + std::uniform_real_distribution realDistributionSmall(1.0f, 4.0f); + std::uniform_int_distribution intDistribution(-100, 100); + std::uniform_int_distribution uintDistribution(0, 100); + + m_logger->log("intrinsics.hlsl TESTS:", system::ILogger::ELL_PERFORMANCE); + for (int i = 0; i < Iterations; ++i) + { + // Set input thest values that will be used in both CPU and GPU tests + IntrinsicsIntputTestValues testInput; + testInput.bitCount = intDistribution(mt); + testInput.crossLhs = float32_t3(realDistribution(mt), realDistribution(mt), realDistribution(mt)); + testInput.crossRhs = float32_t3(realDistribution(mt), realDistribution(mt), realDistribution(mt)); + testInput.clampVal = realDistribution(mt); + testInput.clampMin = realDistributionNeg(mt); + testInput.clampMax = realDistributionPos(mt); + testInput.length = float32_t3(realDistribution(mt), realDistribution(mt), realDistribution(mt)); + testInput.normalize = float32_t3(realDistribution(mt), realDistribution(mt), realDistribution(mt)); + testInput.dotLhs = float32_t3(realDistributionSmall(mt), realDistributionSmall(mt), realDistributionSmall(mt)); + testInput.dotRhs = float32_t3(realDistributionSmall(mt), realDistributionSmall(mt), realDistributionSmall(mt)); + testInput.determinant = float32_t3x3( + realDistributionSmall(mt), realDistributionSmall(mt), realDistributionSmall(mt), + realDistributionSmall(mt), realDistributionSmall(mt), realDistributionSmall(mt), + realDistributionSmall(mt), realDistributionSmall(mt), realDistributionSmall(mt) + ); + testInput.findMSB = realDistribution(mt); + testInput.findLSB = realDistribution(mt); + testInput.inverse = float32_t3x3( + realDistribution(mt), realDistribution(mt), realDistribution(mt), + realDistribution(mt), realDistribution(mt), realDistribution(mt), + realDistribution(mt), realDistribution(mt), realDistribution(mt) + ); + testInput.transpose = float32_t3x3( + realDistribution(mt), realDistribution(mt), realDistribution(mt), + realDistribution(mt), realDistribution(mt), realDistribution(mt), + realDistribution(mt), realDistribution(mt), realDistribution(mt) + ); + testInput.mulLhs = float32_t3x3( + realDistribution(mt), realDistribution(mt), realDistribution(mt), + realDistribution(mt), realDistribution(mt), realDistribution(mt), + realDistribution(mt), realDistribution(mt), realDistribution(mt) + ); + testInput.mulRhs = float32_t3x3( + realDistribution(mt), realDistribution(mt), realDistribution(mt), + realDistribution(mt), realDistribution(mt), realDistribution(mt), + realDistribution(mt), realDistribution(mt), realDistribution(mt) + ); + testInput.minA = realDistribution(mt); + testInput.minB = realDistribution(mt); + testInput.maxA = realDistribution(mt); + testInput.maxB = realDistribution(mt); + testInput.rsqrt = realDistributionPos(mt); + testInput.bitReverse = realDistribution(mt); + testInput.frac = realDistribution(mt); + testInput.mixX = realDistributionNeg(mt); + testInput.mixY = realDistributionPos(mt); + testInput.mixA = realDistributionZeroToOne(mt); + testInput.sign = realDistribution(mt); + testInput.radians = realDistribution(mt); + testInput.degrees = realDistribution(mt); + testInput.stepEdge = realDistribution(mt); + testInput.stepX = realDistribution(mt); + testInput.smoothStepEdge0 = realDistributionNeg(mt); + testInput.smoothStepEdge1 = realDistributionPos(mt); + testInput.smoothStepX = realDistribution(mt); + + testInput.bitCountVec = int32_t3(intDistribution(mt), intDistribution(mt), intDistribution(mt)); + testInput.clampValVec = float32_t3(realDistribution(mt), realDistribution(mt), realDistribution(mt)); + testInput.clampMinVec = float32_t3(realDistributionNeg(mt), realDistributionNeg(mt), realDistributionNeg(mt)); + testInput.clampMaxVec = float32_t3(realDistributionPos(mt), realDistributionPos(mt), realDistributionPos(mt)); + testInput.findMSBVec = uint32_t3(uintDistribution(mt), uintDistribution(mt), uintDistribution(mt)); + testInput.findLSBVec = uint32_t3(uintDistribution(mt), uintDistribution(mt), uintDistribution(mt)); + testInput.minAVec = float32_t3(realDistribution(mt), realDistribution(mt), realDistribution(mt)); + testInput.minBVec = float32_t3(realDistribution(mt), realDistribution(mt), realDistribution(mt)); + testInput.maxAVec = float32_t3(realDistribution(mt), realDistribution(mt), realDistribution(mt)); + testInput.maxBVec = float32_t3(realDistribution(mt), realDistribution(mt), realDistribution(mt)); + testInput.rsqrtVec = float32_t3(realDistributionPos(mt), realDistributionPos(mt), realDistributionPos(mt)); + testInput.bitReverseVec = uint32_t3(uintDistribution(mt), uintDistribution(mt), uintDistribution(mt)); + testInput.fracVec = float32_t3(realDistribution(mt), realDistribution(mt), realDistribution(mt)); + testInput.mixXVec = float32_t3(realDistributionNeg(mt), realDistributionNeg(mt), realDistributionNeg(mt)); + testInput.mixYVec = float32_t3(realDistributionPos(mt), realDistributionPos(mt), realDistributionPos(mt)); + testInput.mixAVec = float32_t3(realDistributionZeroToOne(mt), realDistributionZeroToOne(mt), realDistributionZeroToOne(mt)); + + testInput.signVec = float32_t3(realDistribution(mt), realDistribution(mt), realDistribution(mt)); + testInput.radiansVec = float32_t3(realDistribution(mt), realDistribution(mt), realDistribution(mt)); + testInput.degreesVec = float32_t3(realDistribution(mt), realDistribution(mt), realDistribution(mt)); + testInput.stepEdgeVec = float32_t3(realDistribution(mt), realDistribution(mt), realDistribution(mt)); + testInput.stepXVec = float32_t3(realDistribution(mt), realDistribution(mt), realDistribution(mt)); + testInput.smoothStepEdge0Vec = float32_t3(realDistributionNeg(mt), realDistributionNeg(mt), realDistributionNeg(mt)); + testInput.smoothStepEdge1Vec = float32_t3(realDistributionPos(mt), realDistributionPos(mt), realDistributionPos(mt)); + testInput.smoothStepXVec = float32_t3(realDistribution(mt), realDistribution(mt), realDistribution(mt)); + testInput.faceForwardN = float32_t3(realDistribution(mt), realDistribution(mt), realDistribution(mt)); + testInput.faceForwardI = float32_t3(realDistribution(mt), realDistribution(mt), realDistribution(mt)); + testInput.faceForwardNref = float32_t3(realDistribution(mt), realDistribution(mt), realDistribution(mt)); + testInput.reflectI = float32_t3(realDistribution(mt), realDistribution(mt), realDistribution(mt)); + testInput.reflectN = glm::normalize(float32_t3(realDistribution(mt), realDistribution(mt), realDistribution(mt))); + testInput.refractI = float32_t3(realDistribution(mt), realDistribution(mt), realDistribution(mt)); + testInput.refractN = glm::normalize(float32_t3(realDistribution(mt), realDistribution(mt), realDistribution(mt))); + testInput.refractEta = realDistribution(mt); + + // use std library or glm functions to determine expected test values, the output of functions from intrinsics.hlsl will be verified against these values + IntrinsicsTestValues expected; + expected.bitCount = glm::bitCount(testInput.bitCount); + expected.clamp = glm::clamp(testInput.clampVal, testInput.clampMin, testInput.clampMax); + expected.length = glm::length(testInput.length); + expected.dot = glm::dot(testInput.dotLhs, testInput.dotRhs); + expected.determinant = glm::determinant(reinterpret_cast(testInput.determinant)); + expected.findMSB = glm::findMSB(testInput.findMSB); + expected.findLSB = glm::findLSB(testInput.findLSB); + expected.min = glm::min(testInput.minA, testInput.minB); + expected.max = glm::max(testInput.maxA, testInput.maxB); + expected.rsqrt = (1.0f / std::sqrt(testInput.rsqrt)); + expected.mix = std::lerp(testInput.mixX, testInput.mixY, testInput.mixA); + expected.sign = glm::sign(testInput.sign); + expected.radians = glm::radians(testInput.radians); + expected.degrees = glm::degrees(testInput.degrees); + expected.step = glm::step(testInput.stepEdge, testInput.stepX); + expected.smoothStep = glm::smoothstep(testInput.smoothStepEdge0, testInput.smoothStepEdge1, testInput.smoothStepX); + + expected.frac = testInput.frac - std::floor(testInput.frac); + expected.bitReverse = glm::bitfieldReverse(testInput.bitReverse); + + expected.normalize = glm::normalize(testInput.normalize); + expected.cross = glm::cross(testInput.crossLhs, testInput.crossRhs); + expected.bitCountVec = int32_t3(glm::bitCount(testInput.bitCountVec.x), glm::bitCount(testInput.bitCountVec.y), glm::bitCount(testInput.bitCountVec.z)); + expected.clampVec = float32_t3( + glm::clamp(testInput.clampValVec.x, testInput.clampMinVec.x, testInput.clampMaxVec.x), + glm::clamp(testInput.clampValVec.y, testInput.clampMinVec.y, testInput.clampMaxVec.y), + glm::clamp(testInput.clampValVec.z, testInput.clampMinVec.z, testInput.clampMaxVec.z) + ); + expected.findMSBVec = glm::findMSB(testInput.findMSBVec); + expected.findLSBVec = glm::findLSB(testInput.findLSBVec); + expected.minVec = float32_t3( + glm::min(testInput.minAVec.x, testInput.minBVec.x), + glm::min(testInput.minAVec.y, testInput.minBVec.y), + glm::min(testInput.minAVec.z, testInput.minBVec.z) + ); + expected.maxVec = float32_t3( + glm::max(testInput.maxAVec.x, testInput.maxBVec.x), + glm::max(testInput.maxAVec.y, testInput.maxBVec.y), + glm::max(testInput.maxAVec.z, testInput.maxBVec.z) + ); + expected.rsqrtVec = float32_t3(1.0f / std::sqrt(testInput.rsqrtVec.x), 1.0f / std::sqrt(testInput.rsqrtVec.y), 1.0f / std::sqrt(testInput.rsqrtVec.z)); + expected.bitReverseVec = glm::bitfieldReverse(testInput.bitReverseVec); + expected.fracVec = float32_t3( + testInput.fracVec.x - std::floor(testInput.fracVec.x), + testInput.fracVec.y - std::floor(testInput.fracVec.y), + testInput.fracVec.z - std::floor(testInput.fracVec.z)); + expected.mixVec.x = std::lerp(testInput.mixXVec.x, testInput.mixYVec.x, testInput.mixAVec.x); + expected.mixVec.y = std::lerp(testInput.mixXVec.y, testInput.mixYVec.y, testInput.mixAVec.y); + expected.mixVec.z = std::lerp(testInput.mixXVec.z, testInput.mixYVec.z, testInput.mixAVec.z); + + expected.signVec = glm::sign(testInput.signVec); + expected.radiansVec = glm::radians(testInput.radiansVec); + expected.degreesVec = glm::degrees(testInput.degreesVec); + expected.stepVec = glm::step(testInput.stepEdgeVec, testInput.stepXVec); + expected.smoothStepVec = glm::smoothstep(testInput.smoothStepEdge0Vec, testInput.smoothStepEdge1Vec, testInput.smoothStepXVec); + expected.faceForward = glm::faceforward(testInput.faceForwardN, testInput.faceForwardI, testInput.faceForwardNref); + expected.reflect = glm::reflect(testInput.reflectI, testInput.reflectN); + expected.refract = glm::refract(testInput.refractI, testInput.refractN, testInput.refractEta); + + auto mulGlm = nbl::hlsl::mul(testInput.mulLhs, testInput.mulRhs); + expected.mul = reinterpret_cast(mulGlm); + auto transposeGlm = glm::transpose(reinterpret_cast(testInput.transpose)); + expected.transpose = reinterpret_cast(transposeGlm); + auto inverseGlm = glm::inverse(reinterpret_cast(testInput.inverse)); + expected.inverse = reinterpret_cast(inverseGlm); + + performCpuTests(testInput, expected); + performGpuTests(testInput, expected); + } + m_logger->log("intrinsics.hlsl TESTS DONE.", system::ILogger::ELL_PERFORMANCE); + } + +private: + inline static constexpr int Iterations = 1u; + + void performCpuTests(const IntrinsicsIntputTestValues& commonTestInputValues, const IntrinsicsTestValues& expectedTestValues) + { + IntrinsicsTestValues cpuTestValues; + cpuTestValues.fillTestValues(commonTestInputValues); + verifyTestValues(expectedTestValues, cpuTestValues, ITester::TestType::CPU); + + } + + void performGpuTests(const IntrinsicsIntputTestValues& commonTestInputValues, const IntrinsicsTestValues& expectedTestValues) + { + IntrinsicsTestValues gpuTestValues; + gpuTestValues = dispatch(commonTestInputValues); + verifyTestValues(expectedTestValues, gpuTestValues, ITester::TestType::GPU); + } + + void verifyTestValues(const IntrinsicsTestValues& expectedTestValues, const IntrinsicsTestValues& testValues, ITester::TestType testType) + { + verifyTestValue("bitCount", expectedTestValues.bitCount, testValues.bitCount, testType); + verifyTestValue("clamp", expectedTestValues.clamp, testValues.clamp, testType); + verifyTestValue("length", expectedTestValues.length, testValues.length, testType); + verifyTestValue("dot", expectedTestValues.dot, testValues.dot, testType); + verifyTestValue("determinant", expectedTestValues.determinant, testValues.determinant, testType); + verifyTestValue("findMSB", expectedTestValues.findMSB, testValues.findMSB, testType); + verifyTestValue("findLSB", expectedTestValues.findLSB, testValues.findLSB, testType); + //verifyTestValue("min", expectedTestValues.min, testValues.min, testType); + //verifyTestValue("max", expectedTestValues.max, testValues.max, testType); + verifyTestValue("rsqrt", expectedTestValues.rsqrt, testValues.rsqrt, testType); + verifyTestValue("frac", expectedTestValues.frac, testValues.frac, testType); + verifyTestValue("bitReverse", expectedTestValues.bitReverse, testValues.bitReverse, testType); + verifyTestValue("mix", expectedTestValues.mix, testValues.mix, testType); + verifyTestValue("sign", expectedTestValues.sign, testValues.sign, testType); + verifyTestValue("radians", expectedTestValues.radians, testValues.radians, testType); + verifyTestValue("degrees", expectedTestValues.degrees, testValues.degrees, testType); + verifyTestValue("step", expectedTestValues.step, testValues.step, testType); + verifyTestValue("smoothStep", expectedTestValues.smoothStep, testValues.smoothStep, testType); + + verifyTestVector3dValue("normalize", expectedTestValues.normalize, testValues.normalize, testType); + verifyTestVector3dValue("cross", expectedTestValues.cross, testValues.cross, testType); + verifyTestVector3dValue("bitCountVec", expectedTestValues.bitCountVec, testValues.bitCountVec, testType); + verifyTestVector3dValue("clampVec", expectedTestValues.clampVec, testValues.clampVec, testType); + verifyTestVector3dValue("findMSBVec", expectedTestValues.findMSBVec, testValues.findMSBVec, testType); + verifyTestVector3dValue("findLSBVec", expectedTestValues.findLSBVec, testValues.findLSBVec, testType); + //verifyTestVector3dValue("minVec", expectedTestValues.minVec, testValues.minVec, testType); + //verifyTestVector3dValue("maxVec", expectedTestValues.maxVec, testValues.maxVec, testType); + verifyTestVector3dValue("rsqrtVec", expectedTestValues.rsqrtVec, testValues.rsqrtVec, testType); + verifyTestVector3dValue("bitReverseVec", expectedTestValues.bitReverseVec, testValues.bitReverseVec, testType); + verifyTestVector3dValue("fracVec", expectedTestValues.fracVec, testValues.fracVec, testType); + verifyTestVector3dValue("mixVec", expectedTestValues.mixVec, testValues.mixVec, testType); + + verifyTestVector3dValue("signVec", expectedTestValues.signVec, testValues.signVec, testType); + verifyTestVector3dValue("radiansVec", expectedTestValues.radiansVec, testValues.radiansVec, testType); + verifyTestVector3dValue("degreesVec", expectedTestValues.degreesVec, testValues.degreesVec, testType); + verifyTestVector3dValue("stepVec", expectedTestValues.stepVec, testValues.stepVec, testType); + verifyTestVector3dValue("smoothStepVec", expectedTestValues.smoothStepVec, testValues.smoothStepVec, testType); + verifyTestVector3dValue("faceForward", expectedTestValues.faceForward, testValues.faceForward, testType); + verifyTestVector3dValue("reflect", expectedTestValues.reflect, testValues.reflect, testType); + verifyTestVector3dValue("refract", expectedTestValues.refract, testValues.refract, testType); + + verifyTestMatrix3x3Value("mul", expectedTestValues.mul, testValues.mul, testType); + verifyTestMatrix3x3Value("transpose", expectedTestValues.transpose, testValues.transpose, testType); + verifyTestMatrix3x3Value("inverse", expectedTestValues.inverse, testValues.inverse, testType); + } +}; + +#undef VERIFY_TEST_VALUE +#undef VERIFY_TEST_VECTOR_VALUE + +#endif \ No newline at end of file diff --git a/22_CppCompat/app_resources/common.hlsl b/22_CppCompat/app_resources/common.hlsl index ca43b5afd..381b00563 100644 --- a/22_CppCompat/app_resources/common.hlsl +++ b/22_CppCompat/app_resources/common.hlsl @@ -2,6 +2,9 @@ //// This file is part of the "Nabla Engine". //// For conditions of distribution and use, see copyright notice in nabla.h +#ifndef _NBL_EXAMPLES_TESTS_22_CPP_COMPAT_COMMON_INCLUDED_ +#define _NBL_EXAMPLES_TESTS_22_CPP_COMPAT_COMMON_INCLUDED_ + // because DXC doesn't properly support `_Static_assert` #define STATIC_ASSERT(...) { nbl::hlsl::conditional<__VA_ARGS__, int, void>::type a = 0; } @@ -25,11 +28,352 @@ #include -#define DISABLE_TGMATH_TESTS -#ifndef DISABLE_TGMATH_TESTS -#include -#endif #include #include #include + +#include +#include + +// tgmath.hlsl and intrinsics.hlsl tests + +using namespace nbl::hlsl; +struct TgmathIntputTestValues +{ + float floor; + float isnan; + float isinf; + float powX; + float powY; + float exp; + float exp2; + float log; + float log2; + float absF; + int absI; + float sqrt; + float sin; + float cos; + float acos; + float modf; + float round; + float roundEven; + float trunc; + float ceil; + float fmaX; + float fmaY; + float fmaZ; + float ldexpArg; + int ldexpExp; + float modfStruct; + float frexpStruct; + + float32_t3 floorVec; + float32_t3 isnanVec; + float32_t3 isinfVec; + float32_t3 powXVec; + float32_t3 powYVec; + float32_t3 expVec; + float32_t3 exp2Vec; + float32_t3 logVec; + float32_t3 log2Vec; + float32_t3 absFVec; + int32_t3 absIVec; + float32_t3 sqrtVec; + float32_t3 sinVec; + float32_t3 cosVec; + float32_t3 acosVec; + float32_t3 modfVec; + float32_t3 roundVec; + float32_t3 roundEvenVec; + float32_t3 truncVec; + float32_t3 ceilVec; + float32_t3 fmaXVec; + float32_t3 fmaYVec; + float32_t3 fmaZVec; + float32_t3 ldexpArgVec; + int32_t3 ldexpExpVec; + float32_t3 modfStructVec; + float32_t3 frexpStructVec; +}; + +struct TgmathTestValues +{ + float floor; + int isnan; + int isinf; + float pow; + float exp; + float exp2; + float log; + float log2; + float absF; + int absI; + float sqrt; + float sin; + float cos; + float acos; + float modf; + float round; + float roundEven; + float trunc; + float ceil; + float fma; + float ldexp; + + float32_t3 floorVec; +#ifndef __HLSL_VERSION + nbl::hlsl::vector isnanVec; + nbl::hlsl::vector isinfVec; +#else + vector isnanVec; + vector isinfVec; +#endif + + float32_t3 powVec; + float32_t3 expVec; + float32_t3 exp2Vec; + float32_t3 logVec; + float32_t3 log2Vec; + float32_t3 absFVec; + int32_t3 absIVec; + float32_t3 sqrtVec; + float32_t3 cosVec; + float32_t3 sinVec; + float32_t3 acosVec; + float32_t3 modfVec; + float32_t3 roundVec; + float32_t3 roundEvenVec; + float32_t3 truncVec; + float32_t3 ceilVec; + float32_t3 fmaVec; + float32_t3 ldexpVec; + + ModfOutput modfStruct; + ModfOutput modfStructVec; + FrexpOutput frexpStruct; + FrexpOutput frexpStructVec; + + void fillTestValues(NBL_CONST_REF_ARG(TgmathIntputTestValues) input) + { + floor = nbl::hlsl::floor(input.floor); + isnan = nbl::hlsl::isnan(input.isnan); + isinf = nbl::hlsl::isinf(input.isinf); + pow = nbl::hlsl::pow(input.powX, input.powY); + exp = nbl::hlsl::exp(input.exp); + exp2 = nbl::hlsl::exp2(input.exp2); + log = nbl::hlsl::log(input.log); + log2 = nbl::hlsl::log2(input.log2); + absF = nbl::hlsl::abs(input.absF); + absI = nbl::hlsl::abs(input.absI); + sqrt = nbl::hlsl::sqrt(input.sqrt); + sin = nbl::hlsl::sin(input.sin); + cos = nbl::hlsl::cos(input.cos); + acos = nbl::hlsl::acos(input.acos); + modf = nbl::hlsl::modf(input.modf); + round = nbl::hlsl::round(input.round); + roundEven = nbl::hlsl::roundEven(input.roundEven); + trunc = nbl::hlsl::trunc(input.trunc); + ceil = nbl::hlsl::ceil(input.ceil); + fma = nbl::hlsl::fma(input.fmaX, input.fmaY, input.fmaZ); + ldexp = nbl::hlsl::ldexp(input.ldexpArg, input.ldexpExp); + + floorVec = nbl::hlsl::floor(input.floorVec); + isnanVec = nbl::hlsl::isnan(input.isnanVec); + isinfVec = nbl::hlsl::isinf(input.isinfVec); + powVec = nbl::hlsl::pow(input.powXVec, input.powYVec); + expVec = nbl::hlsl::exp(input.expVec); + exp2Vec = nbl::hlsl::exp2(input.exp2Vec); + logVec = nbl::hlsl::log(input.logVec); + log2Vec = nbl::hlsl::log2(input.log2Vec); + absFVec = nbl::hlsl::abs(input.absFVec); + absIVec = nbl::hlsl::abs(input.absIVec); + sqrtVec = nbl::hlsl::sqrt(input.sqrtVec); + sinVec = nbl::hlsl::sin(input.sinVec); + cosVec = nbl::hlsl::cos(input.cosVec); + acosVec = nbl::hlsl::acos(input.acosVec); + modfVec = nbl::hlsl::modf(input.modfVec); + roundVec = nbl::hlsl::round(input.roundVec); + roundEvenVec = nbl::hlsl::roundEven(input.roundEvenVec); + truncVec = nbl::hlsl::trunc(input.truncVec); + ceilVec = nbl::hlsl::ceil(input.ceilVec); + fmaVec = nbl::hlsl::fma(input.fmaXVec, input.fmaYVec, input.fmaZVec); + ldexpVec = nbl::hlsl::ldexp(input.ldexpArgVec, input.ldexpExpVec); + + modfStruct = nbl::hlsl::modfStruct(input.modfStruct); + modfStructVec = nbl::hlsl::modfStruct(input.modfStructVec); + frexpStruct = nbl::hlsl::frexpStruct(input.frexpStruct); + frexpStructVec = nbl::hlsl::frexpStruct(input.frexpStructVec); + } +}; + +struct IntrinsicsIntputTestValues +{ + int bitCount; + float32_t3 crossLhs; + float32_t3 crossRhs; + float clampVal; + float clampMin; + float clampMax; + float32_t3 length; + float32_t3 normalize; + float32_t3 dotLhs; + float32_t3 dotRhs; + float32_t3x3 determinant; + uint32_t findMSB; + uint32_t findLSB; + float32_t3x3 inverse; + float32_t3x3 transpose; + float32_t3x3 mulLhs; + float32_t3x3 mulRhs; + float minA; + float minB; + float maxA; + float maxB; + float rsqrt; + uint32_t bitReverse; + float frac; + float mixX; + float mixY; + float mixA; + float sign; + float radians; + float degrees; + float stepEdge; + float stepX; + float smoothStepEdge0; + float smoothStepEdge1; + float smoothStepX; + + int32_t3 bitCountVec; + float32_t3 clampValVec; + float32_t3 clampMinVec; + float32_t3 clampMaxVec; + uint32_t3 findMSBVec; + uint32_t3 findLSBVec; + float32_t3 minAVec; + float32_t3 minBVec; + float32_t3 maxAVec; + float32_t3 maxBVec; + float32_t3 rsqrtVec; + uint32_t3 bitReverseVec; + float32_t3 fracVec; + float32_t3 mixXVec; + float32_t3 mixYVec; + float32_t3 mixAVec; + float32_t3 signVec; + float32_t3 radiansVec; + float32_t3 degreesVec; + float32_t3 stepEdgeVec; + float32_t3 stepXVec; + float32_t3 smoothStepEdge0Vec; + float32_t3 smoothStepEdge1Vec; + float32_t3 smoothStepXVec; + float32_t3 faceForwardN; + float32_t3 faceForwardI; + float32_t3 faceForwardNref; + float32_t3 reflectI; + float32_t3 reflectN; + float32_t3 refractI; + float32_t3 refractN; + float refractEta; +}; + +struct IntrinsicsTestValues +{ + int bitCount; + float clamp; + float length; + float dot; + float determinant; + int findMSB; + int findLSB; + float min; + float max; + float rsqrt; + float frac; + uint32_t bitReverse; + float mix; + float sign; + float radians; + float degrees; + float step; + float smoothStep; + + float32_t3 normalize; + float32_t3 cross; + int32_t3 bitCountVec; + float32_t3 clampVec; + uint32_t3 findMSBVec; + uint32_t3 findLSBVec; + float32_t3 minVec; + float32_t3 maxVec; + float32_t3 rsqrtVec; + uint32_t3 bitReverseVec; + float32_t3 fracVec; + float32_t3 mixVec; + float32_t3 signVec; + float32_t3 radiansVec; + float32_t3 degreesVec; + float32_t3 stepVec; + float32_t3 smoothStepVec; + float32_t3 faceForward; + float32_t3 reflect; + float32_t3 refract; + + float32_t3x3 mul; + float32_t3x3 transpose; + float32_t3x3 inverse; + + void fillTestValues(NBL_CONST_REF_ARG(IntrinsicsIntputTestValues) input) + { + bitCount = nbl::hlsl::bitCount(input.bitCount); + cross = nbl::hlsl::cross(input.crossLhs, input.crossRhs); + clamp = nbl::hlsl::clamp(input.clampVal, input.clampMin, input.clampMax); + length = nbl::hlsl::length(input.length); + normalize = nbl::hlsl::normalize(input.normalize); + dot = nbl::hlsl::dot(input.dotLhs, input.dotRhs); + determinant = nbl::hlsl::determinant(input.determinant); + findMSB = nbl::hlsl::findMSB(input.findMSB); + findLSB = nbl::hlsl::findLSB(input.findLSB); + inverse = nbl::hlsl::inverse(input.inverse); + transpose = nbl::hlsl::transpose(input.transpose); + mul = nbl::hlsl::mul(input.mulLhs, input.mulRhs); + // TODO: fix min and max + //min = nbl::hlsl::min(input.minA, input.minB); + //max = nbl::hlsl::max(input.maxA, input.maxB); + rsqrt = nbl::hlsl::rsqrt(input.rsqrt); + bitReverse = nbl::hlsl::bitReverse(input.bitReverse); + frac = nbl::hlsl::frac(input.frac); + mix = nbl::hlsl::mix(input.mixX, input.mixY, input.mixA); + sign = nbl::hlsl::sign(input.sign); + radians = nbl::hlsl::radians(input.radians); + degrees = nbl::hlsl::degrees(input.degrees); + step = nbl::hlsl::step(input.stepEdge, input.stepX); + smoothStep = nbl::hlsl::smoothStep(input.smoothStepEdge0, input.smoothStepEdge1, input.smoothStepX); + + bitCountVec = nbl::hlsl::bitCount(input.bitCountVec); + clampVec = nbl::hlsl::clamp(input.clampValVec, input.clampMinVec, input.clampMaxVec); + findMSBVec = nbl::hlsl::findMSB(input.findMSBVec); + findLSBVec = nbl::hlsl::findLSB(input.findLSBVec); + // TODO: fix min and max + //minVec = nbl::hlsl::min(input.minAVec, input.minBVec); + //maxVec = nbl::hlsl::max(input.maxAVec, input.maxBVec); + rsqrtVec = nbl::hlsl::rsqrt(input.rsqrtVec); + bitReverseVec = nbl::hlsl::bitReverse(input.bitReverseVec); + fracVec = nbl::hlsl::frac(input.fracVec); + mixVec = nbl::hlsl::mix(input.mixXVec, input.mixYVec, input.mixAVec); + + signVec = nbl::hlsl::sign(input.signVec); + radiansVec = nbl::hlsl::radians(input.radiansVec); + degreesVec = nbl::hlsl::degrees(input.degreesVec); + stepVec = nbl::hlsl::step(input.stepEdgeVec, input.stepXVec); + smoothStepVec = nbl::hlsl::smoothStep(input.smoothStepEdge0Vec, input.smoothStepEdge1Vec, input.smoothStepXVec); + faceForward = nbl::hlsl::faceForward(input.faceForwardN, input.faceForwardI, input.faceForwardNref); + reflect = nbl::hlsl::reflect(input.reflectI, input.reflectN); + refract = nbl::hlsl::refract(input.refractI, input.refractN, input.refractEta); + } +}; + +#endif diff --git a/22_CppCompat/app_resources/intrinsicsTest.comp.hlsl b/22_CppCompat/app_resources/intrinsicsTest.comp.hlsl new file mode 100644 index 000000000..3e634ed1f --- /dev/null +++ b/22_CppCompat/app_resources/intrinsicsTest.comp.hlsl @@ -0,0 +1,16 @@ +//// Copyright (C) 2023-2024 - DevSH Graphics Programming Sp. z O.O. +//// This file is part of the "Nabla Engine". +//// For conditions of distribution and use, see copyright notice in nabla.h +#pragma shader_stage(compute) + +#include "common.hlsl" + +[[vk::binding(0, 0)]] RWStructuredBuffer inputTestValues; +[[vk::binding(1, 0)]] RWStructuredBuffer outputTestValues; + +[numthreads(16, 1, 1)] +void main(uint3 invocationID : SV_DispatchThreadID) +{ + if(invocationID.x == 0) + outputTestValues[0].fillTestValues(inputTestValues[0]); +} diff --git a/22_CppCompat/app_resources/tgmathTest.comp.hlsl b/22_CppCompat/app_resources/tgmathTest.comp.hlsl new file mode 100644 index 000000000..d4652ce49 --- /dev/null +++ b/22_CppCompat/app_resources/tgmathTest.comp.hlsl @@ -0,0 +1,16 @@ +//// Copyright (C) 2023-2024 - DevSH Graphics Programming Sp. z O.O. +//// This file is part of the "Nabla Engine". +//// For conditions of distribution and use, see copyright notice in nabla.h +#pragma shader_stage(compute) + +#include "common.hlsl" + +[[vk::binding(0, 0)]] RWStructuredBuffer inputTestValues; +[[vk::binding(1, 0)]] RWStructuredBuffer outputTestValues; + +[numthreads(16, 1, 1)] +void main(uint3 invocationID : SV_DispatchThreadID) +{ + if(invocationID.x == 0) + outputTestValues[0].fillTestValues(inputTestValues[0]); +} diff --git a/22_CppCompat/main.cpp b/22_CppCompat/main.cpp index 0959c9bd3..7b547c253 100644 --- a/22_CppCompat/main.cpp +++ b/22_CppCompat/main.cpp @@ -10,6 +10,7 @@ #include "nbl/application_templates/MonoAssetManagerAndBuiltinResourceApplication.hpp" #include "app_resources/common.hlsl" +#include "Testers.h" using namespace nbl::core; @@ -56,11 +57,31 @@ class CompatibilityTest final : public MonoDeviceApplication, public MonoAssetMa return false; if (!asset_base_t::onAppInitialized(std::move(system))) return false; - + + ITester::PipelineSetupData pplnSetupData; + pplnSetupData.device = m_device; + pplnSetupData.api = m_api; + pplnSetupData.assetMgr = m_assetMgr; + pplnSetupData.logger = m_logger; + pplnSetupData.physicalDevice = m_physicalDevice; + pplnSetupData.computeFamilyIndex = getComputeQueue()->getFamilyIndex(); + + { + CTgmathTester tgmathTester; + pplnSetupData.testShaderPath = "app_resources/tgmathTest.comp.hlsl"; + tgmathTester.setupPipeline(pplnSetupData); + tgmathTester.performTests(); + } + { + CIntrinsicsTester intrinsicsTester; + pplnSetupData.testShaderPath = "app_resources/intrinsicsTest.comp.hlsl"; + intrinsicsTester.setupPipeline(pplnSetupData); + intrinsicsTester.performTests(); + } + m_queue = m_device->getQueue(0, 0); m_commandPool = m_device->createCommandPool(m_queue->getFamilyIndex(), IGPUCommandPool::CREATE_FLAGS::RESET_COMMAND_BUFFER_BIT); m_commandPool->createCommandBuffers(IGPUCommandPool::BUFFER_LEVEL::PRIMARY, { &m_cmdbuf,1 }, smart_refctd_ptr(m_logger)); - smart_refctd_ptr shader; { @@ -211,7 +232,6 @@ class CompatibilityTest final : public MonoDeviceApplication, public MonoAssetMa constexpr auto StartedValue = 0; smart_refctd_ptr progress = m_device->createSemaphore(StartedValue); - m_cmdbuf->reset(IGPUCommandBuffer::RESET_FLAGS::RELEASE_RESOURCES_BIT); m_cmdbuf->begin(IGPUCommandBuffer::USAGE::ONE_TIME_SUBMIT_BIT); @@ -562,7 +582,7 @@ void cpu_tests() auto zero = cross(x,x); auto lenX2 = dot(x,x); //auto z_inv = inverse(z); //busted return type conversion - auto mid = lerp(x,x,0.5f); + auto mid = nbl::hlsl::mix(x,x,0.5f); //auto w = transpose(y); //also busted @@ -761,8 +781,8 @@ void cpu_tests() TEST_CMATH(fdim, 2, type) \ - TEST_CMATH_FOR_TYPE(float32_t) - TEST_CMATH_FOR_TYPE(float64_t) + //TEST_CMATH_FOR_TYPE(float32_t) + //TEST_CMATH_FOR_TYPE(float64_t) #endif std::cout << "cpu tests done\n"; } diff --git a/28_FFTBloom/app_resources/fft_convolve_ifft.hlsl b/28_FFTBloom/app_resources/fft_convolve_ifft.hlsl index ce400c72f..3f1aca571 100644 --- a/28_FFTBloom/app_resources/fft_convolve_ifft.hlsl +++ b/28_FFTBloom/app_resources/fft_convolve_ifft.hlsl @@ -1,5 +1,4 @@ #include "fft_mirror_common.hlsl" -#include "nbl/builtin/hlsl/bitreverse.hlsl" [[vk::binding(3, 0)]] Texture2DArray kernelChannels; [[vk::binding(1, 0)]] SamplerState samplerState; diff --git a/28_FFTBloom/app_resources/kernel_fft_second_axis.hlsl b/28_FFTBloom/app_resources/kernel_fft_second_axis.hlsl index 43513a1c6..8964db9e9 100644 --- a/28_FFTBloom/app_resources/kernel_fft_second_axis.hlsl +++ b/28_FFTBloom/app_resources/kernel_fft_second_axis.hlsl @@ -1,6 +1,5 @@ #include "fft_mirror_common.hlsl" #include "nbl/builtin/hlsl/colorspace/encodeCIEXYZ.hlsl" -#include "nbl/builtin/hlsl/bitreverse.hlsl" [[vk::binding(2, 0)]] RWTexture2DArray kernelChannels; diff --git a/61_UI/include/common.hpp b/61_UI/include/common.hpp index a5def7551..cf8afeea8 100644 --- a/61_UI/include/common.hpp +++ b/61_UI/include/common.hpp @@ -4,7 +4,7 @@ #include // common api -#include "CCamera.hpp" +#include "CFPSCamera.hpp" #include "SimpleWindowedApplication.hpp" #include "CEventCallback.hpp" diff --git a/61_UI/main.cpp b/61_UI/main.cpp index 470d5e723..cd794fedb 100644 --- a/61_UI/main.cpp +++ b/61_UI/main.cpp @@ -3,6 +3,16 @@ // For conditions of distribution and use, see copyright notice in nabla.h #include "common.hpp" +#include "camera/CCubeProjection.hpp" +#include "camera/ICameraControl.hpp" +#include "nbl/builtin/hlsl/projection/projection.hlsl" +#include "nbl/builtin/hlsl/camera/view_matrix.hlsl" +#include "glm/glm/ext/matrix_clip_space.hpp" // TODO: TESTING + +// FPS Camera, TESTS +using matrix_precision_t = float32_t; +using camera_t = CFPSCamera; +using projection_t = IProjection>; // TODO: temporary -> projections will own/reference cameras /* Renders scene texture to an offline @@ -158,28 +168,24 @@ class UISampleApp final : public examples::SimpleWindowedApplication pass.ui.manager->registerListener([this]() -> void { ImGuiIO& io = ImGui::GetIO(); - - camera.setProjectionMatrix([&]() { - static matrix4SIMD projection; - if (isPerspective) - if(isLH) - projection = matrix4SIMD::buildProjectionMatrixPerspectiveFovLH(core::radians(fov), io.DisplaySize.x / io.DisplaySize.y, zNear, zFar); + { + if (isLH) + projection->setMatrix(buildProjectionMatrixPerspectiveFovLH(glm::radians(fov), io.DisplaySize.x / io.DisplaySize.y, zNear, zFar)); else - projection = matrix4SIMD::buildProjectionMatrixPerspectiveFovRH(core::radians(fov), io.DisplaySize.x / io.DisplaySize.y, zNear, zFar); + projection->setMatrix(buildProjectionMatrixPerspectiveFovRH(glm::radians(fov), io.DisplaySize.x / io.DisplaySize.y, zNear, zFar)); + } else { float viewHeight = viewWidth * io.DisplaySize.y / io.DisplaySize.x; - if(isLH) - projection = matrix4SIMD::buildProjectionMatrixOrthoLH(viewWidth, viewHeight, zNear, zFar); + if (isLH) + projection->setMatrix(buildProjectionMatrixOrthoLH(viewWidth, viewHeight, zNear, zFar)); else - projection = matrix4SIMD::buildProjectionMatrixOrthoRH(viewWidth, viewHeight, zNear, zFar); + projection->setMatrix(buildProjectionMatrixOrthoRH(viewWidth, viewHeight, zNear, zFar)); } - - return projection; - }()); + } ImGuizmo::SetOrthographic(false); ImGuizmo::BeginFrame(); @@ -238,15 +244,14 @@ class UISampleApp final : public examples::SimpleWindowedApplication if (viewDirty || firstFrame) { - core::vectorSIMDf cameraPosition(cosf(camYAngle)* cosf(camXAngle)* transformParams.camDistance, sinf(camXAngle)* transformParams.camDistance, sinf(camYAngle)* cosf(camXAngle)* transformParams.camDistance); - core::vectorSIMDf cameraTarget(0.f, 0.f, 0.f); - const static core::vectorSIMDf up(0.f, 1.f, 0.f); + float32_t3 cameraPosition(cosf(camYAngle)* cosf(camXAngle)* transformParams.camDistance, sinf(camXAngle)* transformParams.camDistance, sinf(camYAngle)* cosf(camXAngle)* transformParams.camDistance); + float32_t3 cameraTarget(0.f, 0.f, 0.f); - camera.setPosition(cameraPosition); - camera.setTarget(cameraTarget); - camera.setBackupUpVector(up); - - camera.recomputeViewMatrix(); + // TODO: lets generate events and make it + // happen purely on gimbal manipulation! + + //camera->getGimbal()->setPosition(cameraPosition); + //camera->getGimbal()->setTarget(cameraTarget); firstFrame = false; } @@ -314,35 +319,34 @@ class UISampleApp final : public examples::SimpleWindowedApplication static struct { - core::matrix4SIMD view, projection, model; + float32_t4x4 view, projection, model; } imguizmoM16InOut; - ImGuizmo::SetID(0u); + const auto& projectionMatrix = projection->getMatrix(); + const auto& view = camera->getGimbal().getView().value(); - imguizmoM16InOut.view = core::transpose(matrix4SIMD(camera.getViewMatrix())); - imguizmoM16InOut.projection = core::transpose(camera.getProjectionMatrix()); - imguizmoM16InOut.model = core::transpose(core::matrix4SIMD(pass.scene->object.model)); + ImGuizmo::SetID(0u); + imguizmoM16InOut.view = transpose(getMatrix3x4As4x4(view.matrix)); + imguizmoM16InOut.projection = transpose(projectionMatrix); + imguizmoM16InOut.model = transpose(getMatrix3x4As4x4(pass.scene->object.model)); { if (flipGizmoY) // note we allow to flip gizmo just to match our coordinates imguizmoM16InOut.projection[1][1] *= -1.f; // https://johannesugb.github.io/gpu-programming/why-do-opengl-proj-matrices-fail-in-vulkan/ transformParams.editTransformDecomposition = true; - EditTransform(imguizmoM16InOut.view.pointer(), imguizmoM16InOut.projection.pointer(), imguizmoM16InOut.model.pointer(), transformParams); + EditTransform(&imguizmoM16InOut.view[0][0], &imguizmoM16InOut.projection[0][0], &imguizmoM16InOut.model[0][0], transformParams); } // to Nabla + update camera & model matrices - const auto& view = camera.getViewMatrix(); - const auto& projection = camera.getProjectionMatrix(); - + // TODO: make it more nicely - const_cast(view) = core::transpose(imguizmoM16InOut.view).extractSub3x4(); // a hack, correct way would be to use inverse matrix and get position + target because now it will bring you back to last position & target when switching from gizmo move to manual move (but from manual to gizmo is ok) - camera.setProjectionMatrix(projection); // update concatanated matrix + const_cast(view.matrix) = float32_t3x4(transpose(imguizmoM16InOut.view)); // a hack, correct way would be to use inverse matrix and get position + target because now it will bring you back to last position & target when switching from gizmo move to manual move (but from manual to gizmo is ok) { - static nbl::core::matrix3x4SIMD modelView, normal; - static nbl::core::matrix4SIMD modelViewProjection; + static float32_t3x4 modelView, normal; + static float32_t4x4 modelViewProjection; auto& hook = pass.scene->object; - hook.model = core::transpose(imguizmoM16InOut.model).extractSub3x4(); + hook.model = float32_t3x4(transpose(imguizmoM16InOut.model)); { const auto& references = pass.scene->getResources().objects; const auto type = static_cast(gcIndex); @@ -354,13 +358,17 @@ class UISampleApp final : public examples::SimpleWindowedApplication auto& ubo = hook.viewParameters; - modelView = nbl::core::concatenateBFollowedByA(view, hook.model); - modelView.getSub3x3InverseTranspose(normal); - modelViewProjection = nbl::core::concatenateBFollowedByA(camera.getConcatenatedMatrix(), hook.model); + modelView = concatenateBFollowedByA(view.matrix, hook.model); + + // TODO + //modelView.getSub3x3InverseTranspose(normal); - memcpy(ubo.MVP, modelViewProjection.pointer(), sizeof(ubo.MVP)); - memcpy(ubo.MV, modelView.pointer(), sizeof(ubo.MV)); - memcpy(ubo.NormalMat, normal.pointer(), sizeof(ubo.NormalMat)); + auto concatMatrix = mul(projectionMatrix, getMatrix3x4As4x4(view.matrix)); + modelViewProjection = mul(concatMatrix, getMatrix3x4As4x4(hook.model)); + + memcpy(ubo.MVP, &modelViewProjection[0][0], sizeof(ubo.MVP)); + memcpy(ubo.MV, &modelView[0][0], sizeof(ubo.MV)); + memcpy(ubo.NormalMat, &normal[0][0], sizeof(ubo.NormalMat)); // object meta display { @@ -395,9 +403,18 @@ class UISampleApp final : public examples::SimpleWindowedApplication ImGui::Separator(); }; - addMatrixTable("Model Matrix", "ModelMatrixTable", 3, 4, pass.scene->object.model.pointer()); - addMatrixTable("Camera View Matrix", "ViewMatrixTable", 3, 4, view.pointer()); - addMatrixTable("Camera View Projection Matrix", "ViewProjectionMatrixTable", 4, 4, projection.pointer(), false); + const auto& orientation = camera->getGimbal().getOrthonornalMatrix(); + + addMatrixTable("Model Matrix", "ModelMatrixTable", 3, 4, &pass.scene->object.model[0][0]); + + addMatrixTable("Right", "OrientationRightVec", 1, 3, &camera->getGimbal().getXAxis()[0]); + addMatrixTable("Up", "OrientationUpVec", 1, 3, &camera->getGimbal().getYAxis()[0]); + addMatrixTable("Forward", "OrientationForwardVec", 1, 3, &camera->getGimbal().getZAxis()[0]); + addMatrixTable("Position", "PositionForwardVec", 1, 3, &camera->getGimbal().getPosition()[0]); + + //addMatrixTable("Camera Gimbal Orientation Matrix", "OrientationMatrixTable", 3, 3, &orientation[0][0]); + addMatrixTable("Camera Gimbal View Matrix", "ViewMatrixTable", 3, 4, &view.matrix[0][0]); + addMatrixTable("Camera Gimbal Projection Matrix", "ProjectionMatrixTable", 4, 4, &projectionMatrix[0][0], false); ImGui::End(); } @@ -479,7 +496,14 @@ class UISampleApp final : public examples::SimpleWindowedApplication m_surface->recreateSwapchain(); m_winMgr->show(m_window.get()); oracle.reportBeginFrameRecord(); - camera.mapKeysToArrows(); + + /* + TESTS, TODO: remove all once finished work & integrate with the example properly + */ + + const float32_t3 position(cosf(camYAngle)* cosf(camXAngle)* transformParams.camDistance, sinf(camXAngle)* transformParams.camDistance, sinf(camYAngle)* cosf(camXAngle)* transformParams.camDistance); + projection->setMatrix(buildProjectionMatrixPerspectiveFovLH(glm::radians(fov), float(m_window->getWidth()) / float(m_window->getHeight()), zNear, zFar)); + camera = make_smart_refctd_ptr(position); return true; } @@ -663,9 +687,6 @@ class UISampleApp final : public examples::SimpleWindowedApplication inline void update() { - camera.setMoveSpeed(moveSpeed); - camera.setRotateSpeed(rotateSpeed); - static std::chrono::microseconds previousEventTimestamp{}; m_inputSystem->getDefaultMouse(&mouse); @@ -690,46 +711,36 @@ class UISampleApp final : public examples::SimpleWindowedApplication std::vector keyboard{}; } capturedEvents; - if (move) camera.beginInputProcessing(nextPresentationTimestamp); + mouse.consumeEvents([&](const IMouseEventChannel::range_t& events) -> void { - mouse.consumeEvents([&](const IMouseEventChannel::range_t& events) -> void + for (const auto& e : events) // here capture { - if (move) - camera.mouseProcess(events); // don't capture the events, only let camera handle them with its impl - - for (const auto& e : events) // here capture - { - if (e.timeStamp < previousEventTimestamp) - continue; + if (e.timeStamp < previousEventTimestamp) + continue; - previousEventTimestamp = e.timeStamp; - capturedEvents.mouse.emplace_back(e); + previousEventTimestamp = e.timeStamp; + capturedEvents.mouse.emplace_back(e); - if (e.type == nbl::ui::SMouseEvent::EET_SCROLL) - gcIndex = std::clamp(int16_t(gcIndex) + int16_t(core::sign(e.scrollEvent.verticalScroll)), int64_t(0), int64_t(OT_COUNT - (uint8_t)1u)); - } - }, m_logger.get()); + if (e.type == nbl::ui::SMouseEvent::EET_SCROLL) + gcIndex = std::clamp(int16_t(gcIndex) + int16_t(core::sign(e.scrollEvent.verticalScroll)), int64_t(0), int64_t(OT_COUNT - (uint8_t)1u)); + } + }, m_logger.get()); keyboard.consumeEvents([&](const IKeyboardEventChannel::range_t& events) -> void + { + for (const auto& e : events) // here capture { - if (move) - camera.keyboardProcess(events); // don't capture the events, only let camera handle them with its impl - - for (const auto& e : events) // here capture - { - if (e.timeStamp < previousEventTimestamp) - continue; + if (e.timeStamp < previousEventTimestamp) + continue; - previousEventTimestamp = e.timeStamp; - capturedEvents.keyboard.emplace_back(e); - } - }, m_logger.get()); - } - if (move) camera.endInputProcessing(nextPresentationTimestamp); + previousEventTimestamp = e.timeStamp; + capturedEvents.keyboard.emplace_back(e); + } + }, m_logger.get()); const auto cursorPosition = m_window->getCursorControl()->getPosition(); - nbl::ext::imgui::UI::SUpdateParameters params = + nbl::ext::imgui::UI::SUpdateParameters params = { .mousePosition = nbl::hlsl::float32_t2(cursorPosition.x, cursorPosition.y) - nbl::hlsl::float32_t2(m_window->getX(), m_window->getY()), .displaySize = { m_window->getWidth(), m_window->getHeight() }, @@ -737,6 +748,18 @@ class UISampleApp final : public examples::SimpleWindowedApplication .keyboardEvents = { capturedEvents.keyboard.data(), capturedEvents.keyboard.size() } }; + if (move) + { + static std::vector virtualMouseEvents(CVirtualGimbalEvent::VirtualEventsTypeTable.size()), virtualKeyboardEvents(CVirtualGimbalEvent::VirtualEventsTypeTable.size()); + uint32_t vEventsMouseCount, vEventsKeyboardCount; + + camera->processMouse(virtualMouseEvents.data(), vEventsMouseCount, params.mouseEvents); + camera->processKeyboard(virtualKeyboardEvents.data(), vEventsKeyboardCount, params.keyboardEvents); + + camera->manipulate({ virtualMouseEvents.data(), vEventsMouseCount }); + camera->manipulate({ virtualKeyboardEvents.data(), vEventsKeyboardCount }); + } + pass.ui.manager->update(params); } @@ -780,7 +803,8 @@ class UISampleApp final : public examples::SimpleWindowedApplication C_UI ui; } pass; - Camera camera = Camera(core::vectorSIMDf(0, 0, 0), core::vectorSIMDf(0, 0, 0), core::matrix4SIMD()); + smart_refctd_ptr projection = make_smart_refctd_ptr(); // TMP! + core::smart_refctd_ptr> camera; video::CDumbPresentationOracle oracle; uint16_t gcIndex = {}; // note: this is dirty however since I assume only single object in scene I can leave it now, when this example is upgraded to support multiple objects this needs to be changed diff --git a/62_CAD/Hatch.cpp b/62_CAD/Hatch.cpp index c676ed66e..0d62a0746 100644 --- a/62_CAD/Hatch.cpp +++ b/62_CAD/Hatch.cpp @@ -16,7 +16,7 @@ bool Hatch::Segment::isStraightLineConstantMajor() const p1 = originalBezier->P1[major], p2 = originalBezier->P2[major]; //assert(p0 <= p1 && p1 <= p2); (PRECISION ISSUES ARISE ONCE MORE) - return abs(p1 - p0) <= core::exp2(-24.0) && abs(p2 - p0) <= exp(-24); + return abs(p1 - p0) <= core::exp2(-24.0) && abs(p2 - p0) <= hlsl::exp(-24.0f); } std::array Hatch::Segment::intersect(const Segment& other) const diff --git a/62_CAD/curves.cpp b/62_CAD/curves.cpp index 99438766b..2995859ee 100644 --- a/62_CAD/curves.cpp +++ b/62_CAD/curves.cpp @@ -53,7 +53,7 @@ float64_t ParametricCurve::inverseArcLen(float64_t targetLen, float64_t min, flo float64_t ParametricCurve::differentialArcLen(float64_t t) const { - return length(computeTangent(t)); + return hlsl::length(computeTangent(t)); } float64_t ExplicitCurve::differentialArcLen(float64_t x) const diff --git a/62_CAD/curves.h b/62_CAD/curves.h index f9dbb7a0e..c19fea4ec 100644 --- a/62_CAD/curves.h +++ b/62_CAD/curves.h @@ -6,6 +6,7 @@ #include #include #include +using namespace nbl; using namespace nbl::hlsl; #include @@ -127,7 +128,7 @@ struct CircularArc final : public ParametricCurve CircularArc(float64_t2 v, float64_t sweepAngle) : originY(-v.y), sweepAngle(sweepAngle) { - r = length(v); + r = hlsl::length(v); startAngle = getSign(v.y) * acos(v.x / r); } @@ -135,7 +136,7 @@ struct CircularArc final : public ParametricCurve CircularArc(float64_t2 v) : originY(-v.y) { - r = length(v); + r = hlsl::length(v); startAngle = getSign(v.y) * acos(v.x / r); sweepAngle = -2.0 * getSign(v.y) * acos(abs(originY) / r); } diff --git a/62_CAD/main.cpp b/62_CAD/main.cpp index 35636b86f..b3932efb3 100644 --- a/62_CAD/main.cpp +++ b/62_CAD/main.cpp @@ -31,6 +31,13 @@ using namespace video; #include "HatchGlyphBuilder.h" #include "GeoTexture.h" +#include + +//TODO: remove +#include +#include +#include + #include #define BENCHMARK_TILL_FIRST_FRAME @@ -66,7 +73,7 @@ constexpr std::array cameraExtents = 600.0, // CASE_8 }; -constexpr ExampleMode mode = ExampleMode::CASE_5; +constexpr ExampleMode mode = ExampleMode::CASE_4; class Camera2D { diff --git a/62_CAD/shaders/globals.hlsl b/62_CAD/shaders/globals.hlsl index 586e10a6f..d51f17bde 100644 --- a/62_CAD/shaders/globals.hlsl +++ b/62_CAD/shaders/globals.hlsl @@ -9,6 +9,7 @@ #include #include #include +#include #ifdef __HLSL_VERSION #include diff --git a/66_HLSLBxDFTests/CMakeLists.txt b/66_HLSLBxDFTests/CMakeLists.txt new file mode 100644 index 000000000..d26a90205 --- /dev/null +++ b/66_HLSLBxDFTests/CMakeLists.txt @@ -0,0 +1,28 @@ +set(NBL_INCLUDE_SEARCH_DIRECTORIES + "${CMAKE_CURRENT_SOURCE_DIR}/include" +) + +include(common RESULT_VARIABLE RES) +if(NOT RES) + message(FATAL_ERROR "common.cmake not found. Should be in {repo_root}/cmake directory") +endif() + +nbl_create_executable_project("" "" "${NBL_INCLUDE_SEARCH_DIRECTORIES}" "" "${NBL_EXECUTABLE_PROJECT_CREATION_PCH_TARGET}") + +if(NBL_EMBED_BUILTIN_RESOURCES) + set(_BR_TARGET_ ${EXECUTABLE_NAME}_builtinResourceData) + set(RESOURCE_DIR "app_resources") + + get_filename_component(_SEARCH_DIRECTORIES_ "${CMAKE_CURRENT_SOURCE_DIR}" ABSOLUTE) + get_filename_component(_OUTPUT_DIRECTORY_SOURCE_ "${CMAKE_CURRENT_BINARY_DIR}/src" ABSOLUTE) + get_filename_component(_OUTPUT_DIRECTORY_HEADER_ "${CMAKE_CURRENT_BINARY_DIR}/include" ABSOLUTE) + + file(GLOB_RECURSE BUILTIN_RESOURCE_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}/${RESOURCE_DIR}" CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${RESOURCE_DIR}/*") + foreach(RES_FILE ${BUILTIN_RESOURCE_FILES}) + LIST_BUILTIN_RESOURCE(RESOURCES_TO_EMBED "${RES_FILE}") + endforeach() + + ADD_CUSTOM_BUILTIN_RESOURCES(${_BR_TARGET_} RESOURCES_TO_EMBED "${_SEARCH_DIRECTORIES_}" "${RESOURCE_DIR}" "nbl::this_example::builtin" "${_OUTPUT_DIRECTORY_HEADER_}" "${_OUTPUT_DIRECTORY_SOURCE_}") + + LINK_BUILTIN_RESOURCES_TO_TARGET(${EXECUTABLE_NAME} ${_BR_TARGET_}) +endif() \ No newline at end of file diff --git a/66_HLSLBxDFTests/app_resources/tests.hlsl b/66_HLSLBxDFTests/app_resources/tests.hlsl new file mode 100644 index 000000000..256ed3ce9 --- /dev/null +++ b/66_HLSLBxDFTests/app_resources/tests.hlsl @@ -0,0 +1,479 @@ +#ifndef BXDFTESTS_TESTS_HLSL +#define BXDFTESTS_TESTS_HLSL + +#include "nbl/builtin/hlsl/cpp_compat.hlsl" + +#include "nbl/builtin/hlsl/random/xoroshiro.hlsl" +#include "nbl/builtin/hlsl/random/pcg.hlsl" +#include "nbl/builtin/hlsl/sampling/uniform.hlsl" +#include "nbl/builtin/hlsl/bxdf/common.hlsl" +#include "nbl/builtin/hlsl/bxdf/reflection.hlsl" +#include "nbl/builtin/hlsl/bxdf/transmission.hlsl" + +#ifndef __HLSL_VERSION +#include +#endif + +namespace nbl +{ +namespace hlsl +{ + +using ray_dir_info_t = bxdf::ray_dir_info::SBasic; +using iso_interaction = bxdf::surface_interactions::SIsotropic; +using aniso_interaction = bxdf::surface_interactions::SAnisotropic; +using sample_t = bxdf::SLightSample; +using iso_cache = bxdf::SIsotropicMicrofacetCache; +using aniso_cache = bxdf::SAnisotropicMicrofacetCache; +using quotient_pdf_t = bxdf::quotient_and_pdf; +using spectral_t = vector; + +using bool32_t3 = vector; + +// uint32_t pcg_hash(uint32_t v) +// { +// uint32_t state = v * 747796405u + 2891336453u; +// uint32_t word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u; +// return (word >> 22u) ^ word; +// } + +// uint32_t2 pcg2d_hash(uint32_t v) +// { +// return uint32_t2(pcg_hash(v), pcg_hash(v+1)); +// } + +namespace impl +{ + +inline float rngFloat01(NBL_REF_ARG(nbl::hlsl::Xoroshiro64Star) rng) +{ + return (float)rng() / numeric_limits::max; +} + +template +struct RNGUniformDist; + +template<> +struct RNGUniformDist +{ + static float32_t __call(NBL_REF_ARG(nbl::hlsl::Xoroshiro64Star) rng) + { + return rngFloat01(rng); + } +}; + +template +struct RNGUniformDist> +{ + static vector __call(NBL_REF_ARG(nbl::hlsl::Xoroshiro64Star) rng) + { + vector retval; + for (int i = 0; i < N; i++) + retval[i] = rngFloat01(rng); + return retval; + } +}; + +} + +template +T rngUniformDist(NBL_REF_ARG(nbl::hlsl::Xoroshiro64Star) rng) +{ + return impl::RNGUniformDist::__call(rng); +} + + +struct SBxDFTestResources +{ + static SBxDFTestResources create(uint32_t2 seed) + { + SBxDFTestResources retval; + retval.rng = nbl::hlsl::Xoroshiro64Star::construct(seed); + retval.u = float32_t3(rngUniformDist(retval.rng), 0.0); + + retval.V.direction = nbl::hlsl::normalize(uniform_sphere_generate(rngUniformDist(retval.rng))); + retval.N = nbl::hlsl::normalize(uniform_sphere_generate(rngUniformDist(retval.rng))); + + float32_t2x3 tb = math::frisvad(retval.N); +#ifndef __HLSL_VERSION + const float angle = 2 * numbers::pi * rngUniformDist(retval.rng); + glm::quat rot = glm::angleAxis(angle, retval.N); + retval.T = rot * tb[0]; + retval.B = rot * tb[1]; +#else + retval.T = tb[0]; + retval.B = tb[1]; +#endif + + retval.alpha.x = rngUniformDist(retval.rng); + retval.alpha.y = rngUniformDist(retval.rng); + retval.eta = 1.3; + retval.ior = float32_t2(1.3, 2.0); + retval.luma_coeff = float32_t3(0.2126, 0.7152, 0.0722); // luma coefficients for Rec. 709 + return retval; + } + + // epsilon + float eps = 1e-3; + + nbl::hlsl::Xoroshiro64Star rng; + ray_dir_info_t V; + float32_t3 N; + float32_t3 T; + float32_t3 B; + + float32_t3 u; + float32_t2 alpha; + float eta; + float32_t2 ior; + float32_t3 luma_coeff; +}; + +enum ErrorType : uint32_t +{ + NOERR = 0, + NEGATIVE_VAL, // pdf/quotient/eval < 0 + PDF_ZERO, // pdf = 0 + QUOTIENT_INF, // quotient -> inf + JACOBIAN, + PDF_EVAL_DIFF, + RECIPROCITY +}; + +struct TestBase +{ + void init(uint32_t2 seed) + { + rc = SBxDFTestResources::create(seed); + + isointer = iso_interaction::create(rc.V, rc.N); + anisointer = aniso_interaction::create(isointer, rc.T, rc.B); + } + + virtual void compute() {} + + SBxDFTestResources rc; + + iso_interaction isointer; + aniso_interaction anisointer; + +#ifndef __HLSL_VERSION + std::string name = "base"; +#endif +}; + +struct FailureCallback +{ + virtual void __call(ErrorType error, NBL_REF_ARG(TestBase) failedFor) {} +}; + +template +struct TestBxDFBase : TestBase +{ + BxDF bxdf; +}; + +template +struct TestBxDF : TestBxDFBase +{ + using base_t = TestBxDFBase; + + void initBxDF(SBxDFTestResources _rc) + { + base_t::bxdf = BxDF::create(); // default to lambertian bxdf +#ifndef __HLSL_VERSION + base_t::name = "Lambertian BxDF"; +#endif + } +}; + +template<> +struct TestBxDF> : TestBxDFBase> +{ + using base_t = TestBxDFBase>; + + void initBxDF(SBxDFTestResources _rc) + { + base_t::bxdf = bxdf::reflection::SOrenNayarBxDF::create(_rc.alpha.x); +#ifndef __HLSL_VERSION + base_t::name = "OrenNayar BRDF"; +#endif + } +}; + +template<> +struct TestBxDF> : TestBxDFBase> +{ + using base_t = TestBxDFBase>; + + template + void initBxDF(SBxDFTestResources _rc) + { + if (aniso) + { + base_t::bxdf = bxdf::reflection::SBeckmannBxDF::create(rc.alpha.x,rc.alpha.y,(float32_t3)(rc.ior.x),(float32_t3)(rc.ior.y)); +#ifndef __HLSL_VERSION + base_t::name = "Beckmann Aniso BRDF"; +#endif + } + else + { + base_t::bxdf = bxdf::reflection::SBeckmannBxDF::create(rc.alpha.x,(float32_t3)(rc.ior.x),(float32_t3)(rc.ior.y)); +#ifndef __HLSL_VERSION + base_t::name = "Beckmann BRDF"; +#endif + } + } +}; + +template<> +struct TestBxDF> : TestBxDFBase> +{ + using base_t = TestBxDFBase>; + + template + void initBxDF(SBxDFTestResources _rc) + { + if (aniso) + { + base_t::bxdf = bxdf::reflection::SGGXBxDF::create(rc.alpha.x,rc.alpha.y,(float32_t3)(rc.ior.x),(float32_t3)(rc.ior.y)); +#ifndef __HLSL_VERSION + base_t::name = "GGX Aniso BRDF"; +#endif + } + else + { + base_t::bxdf = bxdf::reflection::SGGXBxDF::create(rc.alpha.x,(float32_t3)(rc.ior.x),(float32_t3)(rc.ior.y)); +#ifndef __HLSL_VERSION + base_t::name = "GGX BRDF"; +#endif + } + } +}; + +template<> +struct TestBxDF> : TestBxDFBase> +{ + using base_t = TestBxDFBase>; + + void initBxDF(SBxDFTestResources _rc) + { + base_t::bxdf = bxdf::transmission::SSmoothDielectricBxDF::create(rc.eta); +#ifndef __HLSL_VERSION + base_t::name = "Smooth dielectric BSDF"; +#endif + } +}; + +template<> +struct TestBxDF> : TestBxDFBase> +{ + using base_t = TestBxDFBase>; + + void initBxDF(SBxDFTestResources _rc) + { + base_t::bxdf = bxdf::transmission::SSmoothDielectricBxDF::create(float32_t3(rc.eta * rc.eta),rc.luma_coeff); +#ifndef __HLSL_VERSION + base_t::name = "Thin smooth dielectric BSDF"; +#endif + } +}; + +template<> +struct TestBxDF> : TestBxDFBase> +{ + using base_t = TestBxDFBase>; + + template + void initBxDF(SBxDFTestResources _rc) + { + if (aniso) + { + base_t::bxdf = bxdf::transmission::SBeckmannDielectricBxDF::create(rc.eta,rc.alpha.x,rc.alpha.y); +#ifndef __HLSL_VERSION + base_t::name = "Beckmann Dielectric Aniso BSDF"; +#endif + } + else + { + base_t::bxdf = bxdf::transmission::SBeckmannDielectricBxDF::create(rc.eta,rc.alpha.x); +#ifndef __HLSL_VERSION + base_t::name = "Beckmann Dielectric BSDF"; +#endif + } + } +}; + +template<> +struct TestBxDF> : TestBxDFBase> +{ + using base_t = TestBxDFBase>; + + template + void initBxDF(SBxDFTestResources _rc) + { + if (aniso) + { + base_t::bxdf = bxdf::transmission::SGGXDielectricBxDF::create(rc.eta,rc.alpha.x,rc.alpha.y); +#ifndef __HLSL_VERSION + base_t::name = "GGX Dielectric Aniso BSDF"; +#endif + } + else + { + base_t::bxdf = bxdf::transmission::SGGXDielectricBxDF::create(rc.eta,rc.alpha.x); +#ifndef __HLSL_VERSION + base_t::name = "GGX Dielectric BSDF"; +#endif + } + } +}; + + +template +struct is_basic_brdf : bool_constant< + is_same>::value || + is_same>::value +> {}; + +template +struct is_microfacet_brdf : bool_constant< + is_same>::value || + is_same>::value +> {}; + +template +struct is_basic_bsdf : bool_constant< + is_same>::value || + is_same>::value || + is_same>::value +> {}; + +template +struct is_microfacet_bsdf : bool_constant< + is_same>::value || + is_same>::value +> {}; + +template +NBL_CONSTEXPR bool is_basic_brdf_v = is_basic_brdf::value; +template +NBL_CONSTEXPR bool is_microfacet_brdf_v = is_microfacet_brdf::value; +template +NBL_CONSTEXPR bool is_basic_bsdf_v = is_basic_bsdf::value; +template +NBL_CONSTEXPR bool is_microfacet_bsdf_v = is_microfacet_bsdf::value; + + +template +struct TestUOffset : TestBxDF +{ + using base_t = TestBxDFBase; + using this_t = TestUOffset; + + void compute() override + { + aniso_cache cache, dummy; + + float32_t3 ux = base_t::rc.u + float32_t3(base_t::rc.eps,0,0); + float32_t3 uy = base_t::rc.u + float32_t3(0,base_t::rc.eps,0); + + if NBL_CONSTEXPR_FUNC (is_basic_brdf_v) + { + s = base_t::bxdf.generate(base_t::anisointer, base_t::rc.u.xy); + sx = base_t::bxdf.generate(base_t::anisointer, ux.xy); + sy = base_t::bxdf.generate(base_t::anisointer, uy.xy); + } + if NBL_CONSTEXPR_FUNC (is_microfacet_brdf_v) + { + s = base_t::bxdf.generate(base_t::anisointer, base_t::rc.u.xy, cache); + sx = base_t::bxdf.generate(base_t::anisointer, ux.xy, dummy); + sy = base_t::bxdf.generate(base_t::anisointer, uy.xy, dummy); + } + if NBL_CONSTEXPR_FUNC (is_basic_bsdf_v) + { + s = base_t::bxdf.generate(base_t::anisointer, base_t::rc.u); + sx = base_t::bxdf.generate(base_t::anisointer, ux); + sy = base_t::bxdf.generate(base_t::anisointer, uy); + } + if NBL_CONSTEXPR_FUNC (is_microfacet_bsdf_v) + { + s = base_t::bxdf.generate(base_t::anisointer, base_t::rc.u, cache); + sx = base_t::bxdf.generate(base_t::anisointer, ux, dummy); + sy = base_t::bxdf.generate(base_t::anisointer, uy, dummy); + } + + if NBL_CONSTEXPR_FUNC (is_basic_brdf_v || is_basic_bsdf_v) + { + pdf = base_t::bxdf.quotient_and_pdf(s, base_t::isointer); + bsdf = float32_t3(base_t::bxdf.eval(s, base_t::isointer)); + } + if NBL_CONSTEXPR_FUNC (is_microfacet_brdf_v || is_microfacet_bsdf_v) + { + if NBL_CONSTEXPR_FUNC (aniso) + { + pdf = base_t::bxdf.quotient_and_pdf(s, base_t::anisointer, cache); + bsdf = float32_t3(base_t::bxdf.eval(s, base_t::anisointer, cache)); + } + else + { + iso_cache isocache = (iso_cache)cache; + pdf = base_t::bxdf.quotient_and_pdf(s, base_t::isointer, isocache); + bsdf = float32_t3(base_t::bxdf.eval(s, base_t::isointer, isocache)); + } + } + } + + ErrorType test() + { + compute(); + + if (nbl::hlsl::abs(pdf.pdf) < base_t::rc.eps) // something generated cannot have 0 probability of getting generated + return PDF_ZERO; + + if (!all(pdf.quotient < (float32_t3)numeric_limits::infinity)) // importance sampler's job to prevent inf + return QUOTIENT_INF; + + if (all(nbl::hlsl::abs(bsdf) < (float32_t3)base_t::rc.eps) || all(pdf.quotient < (float32_t3)base_t::rc.eps)) + return NOERR; // produces an "impossible" sample + + // get jacobian + float32_t2x2 m = float32_t2x2(sx.TdotL - s.TdotL, sy.TdotL - s.TdotL, sx.BdotL - s.BdotL, sy.BdotL - s.BdotL); + float det = nbl::hlsl::determinant(m); + + bool jacobian_test = nbl::hlsl::abs(det*pdf.pdf/s.NdotL) < base_t::rc.eps; + if (!jacobian_test) + return JACOBIAN; + + bool32_t3 diff_test = nbl::hlsl::max(pdf.value() / bsdf, bsdf / pdf.value()) <= (float32_t3)(1 + base_t::rc.eps); + if (!all(diff_test)) + return PDF_EVAL_DIFF; + + return NOERR; + } + + static void run(uint32_t seed, NBL_REF_ARG(FailureCallback) cb) + { + uint32_t2 state = pcg32x2(seed); + + this_t t; + t.init(state); + if NBL_CONSTEXPR_FUNC (is_microfacet_brdf_v || is_microfacet_bsdf_v) + t.template initBxDF(t.rc); + else + t.initBxDF(t.rc); + + ErrorType e = t.test(); + if (e != NOERR) + cb.__call(e, t); + } + + sample_t s, sx, sy; + quotient_pdf_t pdf; + float32_t3 bsdf; +}; + +} +} + +#endif \ No newline at end of file diff --git a/66_HLSLBxDFTests/main.cpp b/66_HLSLBxDFTests/main.cpp new file mode 100644 index 000000000..f91ed5f02 --- /dev/null +++ b/66_HLSLBxDFTests/main.cpp @@ -0,0 +1,73 @@ +#include +#include +#include + +#include + +using namespace nbl::hlsl; + +#include "app_resources/tests.hlsl" + +struct PrintFailureCallback : FailureCallback +{ + void __call(ErrorType error, NBL_REF_ARG(TestBase) failedFor) override + { + switch (error) + { + case NEGATIVE_VAL: + fprintf(stderr, "%s pdf/quotient/eval < 0\n", failedFor.name.c_str()); + break; + case PDF_ZERO: + fprintf(stderr, "%s pdf = 0\n", failedFor.name.c_str()); + break; + case QUOTIENT_INF: + fprintf(stderr, "%s quotient -> inf\n", failedFor.name.c_str()); + break; + case JACOBIAN: + fprintf(stderr, "%s failed the jacobian * pdf test\n", failedFor.name.c_str()); + break; + case PDF_EVAL_DIFF: + fprintf(stderr, "%s quotient * pdf - eval not 0\n", failedFor.name.c_str()); + break; + case RECIPROCITY: + fprintf(stderr, "%s failed the reprocity test\n", failedFor.name.c_str()); + break; + default: + fprintf(stderr, "%s unknown error\n", failedFor.name.c_str()); + } + + for (volatile bool repeat = true; IsDebuggerPresent() && repeat; ) + { + repeat = false; + __debugbreak(); + failedFor.compute(); + } + } +}; + +int main(int argc, char** argv) +{ + std::cout << std::fixed << std::setprecision(4); + + const uint32_t state = 69u; + + PrintFailureCallback cb; + + // test u offset, 2 axis + TestUOffset>::run(state, cb); + TestUOffset>::run(state, cb); + TestUOffset,false>::run(state, cb); + TestUOffset,true>::run(state, cb); + TestUOffset,false>::run(state, cb); + TestUOffset,true>::run(state, cb); + + TestUOffset>::run(state, cb); + //TestUOffset>::run(state); + //TestUOffset>::run(state); + TestUOffset,false>::run(state, cb); + TestUOffset,true>::run(state, cb); + TestUOffset,false>::run(state, cb); + TestUOffset,true>::run(state, cb); + + return 0; +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 935354ed7..6292fa414 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -91,6 +91,7 @@ if(NBL_BUILD_EXAMPLES) add_subdirectory(64_EmulatedFloatTest EXCLUDE_FROM_ALL) add_subdirectory(0_ImportanceSamplingEnvMaps EXCLUDE_FROM_ALL) #TODO: integrate back into 42 + add_subdirectory(66_HLSLBxDFTests EXCLUDE_FROM_ALL) add_subdirectory(67_RayQueryGeometry EXCLUDE_FROM_ALL) add_subdirectory(70_FLIPFluids EXCLUDE_FROM_ALL) diff --git a/common/include/CCamera.hpp b/common/include/CCamera.hpp deleted file mode 100644 index 1b0fe9c0f..000000000 --- a/common/include/CCamera.hpp +++ /dev/null @@ -1,326 +0,0 @@ -// Copyright (C) 2018-2020 - DevSH Graphics Programming Sp. z O.O. -// This file is part of the "Nabla Engine". -// For conditions of distribution and use, see copyright notice in nabla.h - -#ifndef _CAMERA_IMPL_ -#define _CAMERA_IMPL_ - -#include -#include -#include -#include -#include - -class Camera -{ -public: - Camera() = default; - Camera(const nbl::core::vectorSIMDf& position, const nbl::core::vectorSIMDf& lookat, const nbl::core::matrix4SIMD& projection, float moveSpeed = 1.0f, float rotateSpeed = 1.0f, const nbl::core::vectorSIMDf& upVec = nbl::core::vectorSIMDf(0.0f, 1.0f, 0.0f), const nbl::core::vectorSIMDf& backupUpVec = nbl::core::vectorSIMDf(0.5f, 1.0f, 0.0f)) - : position(position) - , initialPosition(position) - , target(lookat) - , initialTarget(lookat) - , firstUpdate(true) - , moveSpeed(moveSpeed) - , rotateSpeed(rotateSpeed) - , upVector(upVec) - , backupUpVector(backupUpVec) - { - initDefaultKeysMap(); - allKeysUp(); - setProjectionMatrix(projection); - recomputeViewMatrix(); - } - - ~Camera() = default; - - enum E_CAMERA_MOVE_KEYS : uint8_t - { - ECMK_MOVE_FORWARD = 0, - ECMK_MOVE_BACKWARD, - ECMK_MOVE_LEFT, - ECMK_MOVE_RIGHT, - ECMK_COUNT, - }; - - inline void mapKeysToWASD() - { - keysMap[ECMK_MOVE_FORWARD] = nbl::ui::EKC_W; - keysMap[ECMK_MOVE_BACKWARD] = nbl::ui::EKC_S; - keysMap[ECMK_MOVE_LEFT] = nbl::ui::EKC_A; - keysMap[ECMK_MOVE_RIGHT] = nbl::ui::EKC_D; - } - - inline void mapKeysToArrows() - { - keysMap[ECMK_MOVE_FORWARD] = nbl::ui::EKC_UP_ARROW; - keysMap[ECMK_MOVE_BACKWARD] = nbl::ui::EKC_DOWN_ARROW; - keysMap[ECMK_MOVE_LEFT] = nbl::ui::EKC_LEFT_ARROW; - keysMap[ECMK_MOVE_RIGHT] = nbl::ui::EKC_RIGHT_ARROW; - } - - inline void mapKeysCustom(std::array& map) { keysMap = map; } - - inline const nbl::core::matrix4SIMD& getProjectionMatrix() const { return projMatrix; } - inline const nbl::core::matrix3x4SIMD& getViewMatrix() const { return viewMatrix; } - inline const nbl::core::matrix4SIMD& getConcatenatedMatrix() const { return concatMatrix; } - - inline void setProjectionMatrix(const nbl::core::matrix4SIMD& projection) - { - projMatrix = projection; - - const auto hlslMatMap = *reinterpret_cast(&projMatrix); // TEMPORARY TILL THE CAMERA CLASS IS REFACTORED TO WORK WITH HLSL MATRICIES! - { - leftHanded = nbl::hlsl::determinant(hlslMatMap) < 0.f; - } - concatMatrix = nbl::core::matrix4SIMD::concatenateBFollowedByAPrecisely(projMatrix, nbl::core::matrix4SIMD(viewMatrix)); - } - - inline void setPosition(const nbl::core::vectorSIMDf& pos) - { - position.set(pos); - recomputeViewMatrix(); - } - - inline const nbl::core::vectorSIMDf& getPosition() const { return position; } - - inline void setTarget(const nbl::core::vectorSIMDf& pos) - { - target.set(pos); - recomputeViewMatrix(); - } - - inline const nbl::core::vectorSIMDf& getTarget() const { return target; } - - inline void setUpVector(const nbl::core::vectorSIMDf& up) { upVector = up; } - - inline void setBackupUpVector(const nbl::core::vectorSIMDf& up) { backupUpVector = up; } - - inline const nbl::core::vectorSIMDf& getUpVector() const { return upVector; } - - inline const nbl::core::vectorSIMDf& getBackupUpVector() const { return backupUpVector; } - - inline const float getMoveSpeed() const { return moveSpeed; } - - inline void setMoveSpeed(const float _moveSpeed) { moveSpeed = _moveSpeed; } - - inline const float getRotateSpeed() const { return rotateSpeed; } - - inline void setRotateSpeed(const float _rotateSpeed) { rotateSpeed = _rotateSpeed; } - - inline void recomputeViewMatrix() - { - nbl::core::vectorSIMDf pos = position; - nbl::core::vectorSIMDf localTarget = nbl::core::normalize(target - pos); - - // if upvector and vector to the target are the same, we have a - // problem. so solve this problem: - nbl::core::vectorSIMDf up = nbl::core::normalize(upVector); - nbl::core::vectorSIMDf cross = nbl::core::cross(localTarget, up); - bool upVectorNeedsChange = nbl::core::lengthsquared(cross)[0] == 0; - if (upVectorNeedsChange) - up = nbl::core::normalize(backupUpVector); - - if (leftHanded) - viewMatrix = nbl::core::matrix3x4SIMD::buildCameraLookAtMatrixLH(pos, target, up); - else - viewMatrix = nbl::core::matrix3x4SIMD::buildCameraLookAtMatrixRH(pos, target, up); - concatMatrix = nbl::core::matrix4SIMD::concatenateBFollowedByAPrecisely(projMatrix, nbl::core::matrix4SIMD(viewMatrix)); - } - - inline bool getLeftHanded() const { return leftHanded; } - -public: - - void mouseProcess(const nbl::ui::IMouseEventChannel::range_t& events) - { - for (auto eventIt=events.begin(); eventIt!=events.end(); eventIt++) - { - auto ev = *eventIt; - - if(ev.type == nbl::ui::SMouseEvent::EET_CLICK && ev.clickEvent.mouseButton == nbl::ui::EMB_LEFT_BUTTON) - if(ev.clickEvent.action == nbl::ui::SMouseEvent::SClickEvent::EA_PRESSED) - mouseDown = true; - else if (ev.clickEvent.action == nbl::ui::SMouseEvent::SClickEvent::EA_RELEASED) - mouseDown = false; - - if(ev.type == nbl::ui::SMouseEvent::EET_MOVEMENT && mouseDown) - { - nbl::core::vectorSIMDf pos = getPosition(); - nbl::core::vectorSIMDf localTarget = getTarget() - pos; - - // Get Relative Rotation for localTarget in Radians - float relativeRotationX, relativeRotationY; - relativeRotationY = atan2(localTarget.X, localTarget.Z); - const double z1 = nbl::core::sqrt(localTarget.X*localTarget.X + localTarget.Z*localTarget.Z); - relativeRotationX = atan2(z1, localTarget.Y) - nbl::core::PI()/2; - - constexpr float RotateSpeedScale = 0.003f; - relativeRotationX -= ev.movementEvent.relativeMovementY * rotateSpeed * RotateSpeedScale * -1.0f; - float tmpYRot = ev.movementEvent.relativeMovementX * rotateSpeed * RotateSpeedScale * -1.0f; - - if (leftHanded) - relativeRotationY -= tmpYRot; - else - relativeRotationY += tmpYRot; - - const double MaxVerticalAngle = nbl::core::radians(88.0f); - - if (relativeRotationX > MaxVerticalAngle*2 && relativeRotationX < 2 * nbl::core::PI()-MaxVerticalAngle) - relativeRotationX = 2 * nbl::core::PI()-MaxVerticalAngle; - else - if (relativeRotationX > MaxVerticalAngle && relativeRotationX < 2 * nbl::core::PI()-MaxVerticalAngle) - relativeRotationX = MaxVerticalAngle; - - localTarget.set(0,0, nbl::core::max(1.f, nbl::core::length(pos)[0]), 1.f); - - nbl::core::matrix3x4SIMD mat; - mat.setRotation(nbl::core::quaternion(relativeRotationX, relativeRotationY, 0)); - mat.transformVect(localTarget); - - setTarget(localTarget + pos); - } - } - } - - void keyboardProcess(const nbl::ui::IKeyboardEventChannel::range_t& events) - { - for(uint32_t k = 0; k < E_CAMERA_MOVE_KEYS::ECMK_COUNT; ++k) - perActionDt[k] = 0.0; - - /* - * If a Key was already being held down from previous frames - * Compute with this assumption that the key will be held down for this whole frame as well, - * And If an UP event was sent It will get subtracted it from this value. (Currently Disabled Because we Need better Oracle) - */ - - for(uint32_t k = 0; k < E_CAMERA_MOVE_KEYS::ECMK_COUNT; ++k) - if(keysDown[k]) - { - auto timeDiff = std::chrono::duration_cast(nextPresentationTimeStamp - lastVirtualUpTimeStamp).count(); - assert(timeDiff >= 0); - perActionDt[k] += timeDiff; - } - - for (auto eventIt=events.begin(); eventIt!=events.end(); eventIt++) - { - const auto ev = *eventIt; - - // accumulate the periods for which a key was down - const auto timeDiff = std::chrono::duration_cast(nextPresentationTimeStamp - ev.timeStamp).count(); - assert(timeDiff >= 0); - - // handle camera movement - for (const auto logicalKey : { ECMK_MOVE_FORWARD, ECMK_MOVE_BACKWARD, ECMK_MOVE_LEFT, ECMK_MOVE_RIGHT }) - { - const auto code = keysMap[logicalKey]; - - if (ev.keyCode == code) - { - if (ev.action == nbl::ui::SKeyboardEvent::ECA_PRESSED && !keysDown[logicalKey]) - { - perActionDt[logicalKey] += timeDiff; - keysDown[logicalKey] = true; - } - else if (ev.action == nbl::ui::SKeyboardEvent::ECA_RELEASED) - { - // perActionDt[logicalKey] -= timeDiff; - keysDown[logicalKey] = false; - } - } - } - - // handle reset to default state - if (ev.keyCode == nbl::ui::EKC_HOME) - if (ev.action == nbl::ui::SKeyboardEvent::ECA_RELEASED) - { - position = initialPosition; - target = initialTarget; - recomputeViewMatrix(); - } - } - } - - void beginInputProcessing(std::chrono::microseconds _nextPresentationTimeStamp) - { - nextPresentationTimeStamp = _nextPresentationTimeStamp; - return; - } - - void endInputProcessing(std::chrono::microseconds _nextPresentationTimeStamp) - { - nbl::core::vectorSIMDf pos = getPosition(); - nbl::core::vectorSIMDf localTarget = getTarget() - pos; - - if (!firstUpdate) - { - nbl::core::vectorSIMDf movedir = localTarget; - movedir.makeSafe3D(); - movedir = nbl::core::normalize(movedir); - - constexpr float MoveSpeedScale = 0.02f; - - pos += movedir * perActionDt[E_CAMERA_MOVE_KEYS::ECMK_MOVE_FORWARD] * moveSpeed * MoveSpeedScale; - pos -= movedir * perActionDt[E_CAMERA_MOVE_KEYS::ECMK_MOVE_BACKWARD] * moveSpeed * MoveSpeedScale; - - // strafing - - // if upvector and vector to the target are the same, we have a - // problem. so solve this problem: - nbl::core::vectorSIMDf up = nbl::core::normalize(upVector); - nbl::core::vectorSIMDf cross = nbl::core::cross(localTarget, up); - bool upVectorNeedsChange = nbl::core::lengthsquared(cross)[0] == 0; - if (upVectorNeedsChange) - { - up = nbl::core::normalize(backupUpVector); - } - - nbl::core::vectorSIMDf strafevect = localTarget; - if (leftHanded) - strafevect = nbl::core::cross(strafevect, up); - else - strafevect = nbl::core::cross(up, strafevect); - - strafevect = nbl::core::normalize(strafevect); - - pos += strafevect * perActionDt[E_CAMERA_MOVE_KEYS::ECMK_MOVE_LEFT] * moveSpeed * MoveSpeedScale; - pos -= strafevect * perActionDt[E_CAMERA_MOVE_KEYS::ECMK_MOVE_RIGHT] * moveSpeed * MoveSpeedScale; - } - else - firstUpdate = false; - - setPosition(pos); - setTarget(localTarget+pos); - - lastVirtualUpTimeStamp = nextPresentationTimeStamp; - } - -private: - - inline void initDefaultKeysMap() { mapKeysToWASD(); } - - inline void allKeysUp() - { - for (uint32_t i=0; i< E_CAMERA_MOVE_KEYS::ECMK_COUNT; ++i) - keysDown[i] = false; - - mouseDown = false; - } - -private: - nbl::core::vectorSIMDf initialPosition, initialTarget, position, target, upVector, backupUpVector; // TODO: make first 2 const + add default copy constructor - nbl::core::matrix3x4SIMD viewMatrix; - nbl::core::matrix4SIMD concatMatrix, projMatrix; - - float moveSpeed, rotateSpeed; - bool leftHanded, firstUpdate = true, mouseDown = false; - - std::array keysMap = { {nbl::ui::EKC_NONE} }; // map camera E_CAMERA_MOVE_KEYS to corresponding Nabla key codes, by default camera uses WSAD to move - // TODO: make them use std::array - bool keysDown[E_CAMERA_MOVE_KEYS::ECMK_COUNT] = {}; - double perActionDt[E_CAMERA_MOVE_KEYS::ECMK_COUNT] = {}; // durations for which the key was being held down from lastVirtualUpTimeStamp(=last "guessed" presentation time) to nextPresentationTimeStamp - - std::chrono::microseconds nextPresentationTimeStamp, lastVirtualUpTimeStamp; -}; - -#endif // _CAMERA_IMPL_ \ No newline at end of file diff --git a/common/include/CFPSCamera.hpp b/common/include/CFPSCamera.hpp new file mode 100644 index 000000000..58e07e1c0 --- /dev/null +++ b/common/include/CFPSCamera.hpp @@ -0,0 +1,118 @@ +// Copyright (C) 2018-2020 - DevSH Graphics Programming Sp. z O.O. +// This file is part of the "Nabla Engine". +// For conditions of distribution and use, see copyright notice in nabla.h + +#ifndef _C_CAMERA_HPP_ +#define _C_CAMERA_HPP_ + +#include "ICamera.hpp" + +namespace nbl::hlsl // TODO: DIFFERENT NAMESPACE +{ + +// FPS Camera +template +class CFPSCamera final : public ICamera +{ +public: + using base_t = ICamera; + using traits_t = typename base_t::Traits; + + CFPSCamera(const float32_t3& position, glm::quat orientation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f)) + : base_t(), m_gimbal({ .position = position, .orientation = orientation, .withView = true }) + { + initKeysToEvent(); + } + ~CFPSCamera() = default; + + const typename traits_t::gimbal_t& getGimbal() override + { + return m_gimbal; + } + + virtual void manipulate(std::span virtualEvents) override + { + if (!virtualEvents.size()) + return; + + constexpr float MoveSpeedScale = 0.01f, RotateSpeedScale = 0.003f, MaxVerticalAngle = glm::radians(88.0f), MinVerticalAngle = -MaxVerticalAngle; + const auto& gForward = m_gimbal.getZAxis(), gRight = m_gimbal.getXAxis(); + + struct + { + float dPitch = 0.f, dYaw = 0.f; + float32_t3 dMove = { 0.f, 0.f, 0.f }; + } accumulated; + + for (const auto& event : virtualEvents) + { + const float moveScalar = event.magnitude * MoveSpeedScale; + const float rotateScalar = event.magnitude * RotateSpeedScale; + + switch (event.type) + { + case CVirtualGimbalEvent::MoveForward: + accumulated.dMove += gForward * moveScalar; + break; + case CVirtualGimbalEvent::MoveBackward: + accumulated.dMove -= gForward * moveScalar; + break; + case CVirtualGimbalEvent::MoveRight: + accumulated.dMove += gRight * moveScalar; + break; + case CVirtualGimbalEvent::MoveLeft: + accumulated.dMove -= gRight * moveScalar; + break; + case CVirtualGimbalEvent::TiltUp: + accumulated.dPitch += rotateScalar; + break; + case CVirtualGimbalEvent::TiltDown: + accumulated.dPitch -= rotateScalar; + break; + case CVirtualGimbalEvent::PanRight: + accumulated.dYaw += rotateScalar; + break; + case CVirtualGimbalEvent::PanLeft: + accumulated.dYaw -= rotateScalar; + break; + default: + break; + } + } + + float currentPitch = atan2(glm::length(glm::vec2(gForward.x, gForward.z)), gForward.y) - glm::half_pi(); + float currentYaw = atan2(gForward.x, gForward.z); + + currentPitch = std::clamp(currentPitch + accumulated.dPitch, MinVerticalAngle, MaxVerticalAngle); + currentYaw += accumulated.dYaw; + + glm::quat orientation = glm::quat(glm::vec3(currentPitch, currentYaw, 0.0f)); + + m_gimbal.begin(); + { + m_gimbal.setOrientation(orientation); + m_gimbal.move(accumulated.dMove); + } + m_gimbal.end(); + } + +private: + void initKeysToEvent() override + { + traits_t::controller_t::updateKeysToEvent([](CVirtualGimbalEvent::keys_to_virtual_events_t& keys) + { + keys[CVirtualGimbalEvent::MoveForward] = ui::E_KEY_CODE::EKC_W; + keys[CVirtualGimbalEvent::MoveBackward] = ui::E_KEY_CODE::EKC_S; + keys[CVirtualGimbalEvent::MoveLeft] = ui::E_KEY_CODE::EKC_A; + keys[CVirtualGimbalEvent::MoveRight] = ui::E_KEY_CODE::EKC_D; + keys[CVirtualGimbalEvent::MoveUp] = ui::E_KEY_CODE::EKC_SPACE; + keys[CVirtualGimbalEvent::MoveDown] = ui::E_KEY_CODE::EKC_LEFT_SHIFT; + }); + } + + traits_t::gimbal_t m_gimbal; +}; + +} + +#endif // _C_CAMERA_HPP_ \ No newline at end of file diff --git a/common/include/CGeomtryCreatorScene.hpp b/common/include/CGeomtryCreatorScene.hpp index 0d9bc6edd..11ff5558e 100644 --- a/common/include/CGeomtryCreatorScene.hpp +++ b/common/include/CGeomtryCreatorScene.hpp @@ -1098,7 +1098,7 @@ class ResourceBuilder struct ObjectDrawHookCpu { - nbl::core::matrix3x4SIMD model; + nbl::hlsl::float32_t3x4 model; nbl::asset::SBasicViewParameters viewParameters; ObjectMeta meta; }; diff --git a/common/include/ICamera.hpp b/common/include/ICamera.hpp new file mode 100644 index 000000000..4faebe536 --- /dev/null +++ b/common/include/ICamera.hpp @@ -0,0 +1,41 @@ +// Copyright (C) 2018-2020 - DevSH Graphics Programming Sp. z O.O. +// This file is part of the "Nabla Engine". +// For conditions of distribution and use, see copyright notice in nabla.h + +#ifndef _I_CAMERA_HPP_ +#define _I_CAMERA_HPP_ + +#include +#include +#include +#include +#include + +#include "camera/ICameraControl.hpp" + +namespace nbl::hlsl // TODO: DIFFERENT NAMESPACE +{ + +template +class ICamera : public ICameraController +{ +public: + using base_t = typename ICameraController; + + struct Traits + { + using controller_t = base_t; + using gimbal_t = typename controller_t::CGimbal; + using matrix_precision_t = typename T; // TODO: actually all vectors/scalars should have precision type T and because of projection matrix constraints allowed is only float32_t & float64_t + }; + + ICamera() : base_t() {} + ~ICamera() = default; + + // Returns a gimbal which *models the camera view*, note that a camera type implementation may have multiple gimbals under the hood + virtual const Traits::gimbal_t& getGimbal() = 0u; +}; + +} + +#endif // _I_CAMERA_HPP_ \ No newline at end of file diff --git a/common/include/IRange.hpp b/common/include/IRange.hpp new file mode 100644 index 000000000..5c6fe751b --- /dev/null +++ b/common/include/IRange.hpp @@ -0,0 +1,29 @@ +#ifndef _NBL_IRANGE_HPP_ +#define _NBL_IRANGE_HPP_ + +namespace nbl::hlsl +{ + +template +concept GeneralPurposeRange = requires +{ + typename std::ranges::range_value_t; +}; + +//! Interface class for a general purpose range +template +class IRange +{ +public: + using range_t = Range; + using range_value_t = std::ranges::range_value_t; + + IRange(range_t&& range) : m_range(std::move(range)) {} + +protected: + range_t m_range; +}; + +} // namespace nbl::hlsl + +#endif // _NBL_IRANGE_HPP_ \ No newline at end of file diff --git a/common/include/camera/CCubeProjection.hpp b/common/include/camera/CCubeProjection.hpp new file mode 100644 index 000000000..e09d01dce --- /dev/null +++ b/common/include/camera/CCubeProjection.hpp @@ -0,0 +1,36 @@ +#ifndef _NBL_CCUBE_PROJECTION_HPP_ +#define _NBL_CCUBE_PROJECTION_HPP_ + +#include "ILinearProjection.hpp" + +namespace nbl::hlsl +{ + +template +struct CCubeProjectionConstraints +{ + using matrix_t = typename T; + using projection_t = typename IProjection; + using projection_range_t = std::array, 6u>; + using base_t = ILinearProjection; +}; + +//! Class providing linear cube projections with projection matrix per face of a cube, each projection matrix represents a single view-port +template +class CCubeProjection : public CCubeProjectionConstraints::base_t +{ +public: + using constraints_t = CCubeProjectionConstraints; + using base_t = typename constraints_t::base_t; + + CCubeProjection(constraints_t::projection_range_t&& projections = {}) : base_t(std::move(projections)) {} + + constraints_t::projection_range_t& getCubeFaceProjections() + { + return base_t::getViewportProjections(); + } +}; + +} // nbl::hlsl namespace + +#endif // _NBL_CCUBE_PROJECTION_HPP_ \ No newline at end of file diff --git a/common/include/camera/ICameraControl.hpp b/common/include/camera/ICameraControl.hpp new file mode 100644 index 000000000..94e612bf0 --- /dev/null +++ b/common/include/camera/ICameraControl.hpp @@ -0,0 +1,415 @@ +#ifndef _NBL_I_CAMERA_CONTROLLER_HPP_ +#define _NBL_I_CAMERA_CONTROLLER_HPP_ + +#include "IProjection.hpp" +#include "glm/glm/ext/matrix_transform.hpp" // TODO: TEMPORARY!!! whatever used will be moved to cpp +#include "glm/glm/gtc/quaternion.hpp" +#include "nbl/builtin/hlsl/matrix_utils/transformation_matrix_utils.hlsl" + +// TODO: DIFFERENT NAMESPACE +namespace nbl::hlsl +{ + +struct CVirtualGimbalEvent +{ + //! Virtual event representing a gimbal manipulation + enum VirtualEventType : uint8_t + { + // Strafe forward + MoveForward = 0, + + // Strafe backward + MoveBackward, + + // Strafe left + MoveLeft, + + // Strafe right + MoveRight, + + // Strafe up + MoveUp, + + // Strafe down + MoveDown, + + // Tilt the camera upward (pitch) + TiltUp, + + // Tilt the camera downward (pitch) + TiltDown, + + // Rotate the camera left around the vertical axis (yaw) + PanLeft, + + // Rotate the camera right around the vertical axis (yaw) + PanRight, + + // Roll the camera counterclockwise around the forward axis (roll) + RollLeft, + + // Roll the camera clockwise around the forward axis (roll) + RollRight, + + EventsCount + }; + + using manipulation_encode_t = float64_t; + using keys_to_virtual_events_t = std::array; + + VirtualEventType type; + manipulation_encode_t magnitude; + + static inline constexpr auto VirtualEventsTypeTable = []() + { + std::array output; + + for (uint16_t i = 0u; i < EventsCount; ++i) + { + output[i] = static_cast(i); + } + + return output; + }(); +}; + +template +class ICameraController : virtual public core::IReferenceCounted +{ +public: + using matrix_precision_t = typename T; + + class CGimbal + { + public: + struct SCreationParameters + { + float32_t3 position; + glm::quat orientation = glm::quat(1.0f, 0.0f, 0.0f, 0.0f); + bool withView = true; + }; + + // Gimbal's view matrix consists of an orthonormal basis (https://en.wikipedia.org/wiki/Orthonormal_basis) + // for orientation and a translation component that positions the world relative to the gimbal's position. + // Any camera type is supposed to manipulate a position & orientation of a gimbal + // with "virtual events" which model its view bound to the camera + struct SView + { + matrix matrix = {}; + bool isLeftHandSystem = true; + }; + + CGimbal(SCreationParameters&& parameters) + : m_position(parameters.position), m_orientation(parameters.orientation) + { + updateOrthonormalOrientationBase(); + + if (parameters.withView) + { + m_view = std::optional(SView{}); // RVO + updateView(); + } + } + + void begin() + { + m_isManipulating = true; + m_counter = 0u; + } + + inline void setPosition(const float32_t3& position) + { + assert(m_isManipulating); // TODO: log error and return without doing nothing + + if (m_position != position) + m_counter++; + + m_position = position; + } + + inline void setOrientation(const glm::quat& orientation) + { + assert(m_isManipulating); // TODO: log error and return without doing nothing + + if(m_orientation != orientation) + m_counter++; + + m_orientation = glm::normalize(orientation); + updateOrthonormalOrientationBase(); + } + + inline void rotate(const float32_t3& axis, float dRadians) + { + assert(m_isManipulating); // TODO: log error and return without doing nothing + + if(dRadians) + m_counter++; + + glm::quat dRotation = glm::angleAxis(dRadians, axis); + m_orientation = glm::normalize(dRotation * m_orientation); + updateOrthonormalOrientationBase(); + } + + inline void move(float32_t3 delta) + { + assert(m_isManipulating); // TODO: log error and return without doing nothing + + auto newPosition = m_position + delta; + + if (newPosition != m_position) + m_counter++; + + m_position = newPosition; + } + + void end() + { + m_isManipulating = false; + + if (m_counter > 0u) + updateView(); + + m_counter = 0u; + } + + // Position of gimbal + inline const float32_t3& getPosition() const { return m_position; } + + // Orientation of gimbal + inline const glm::quat& getOrientation() const { return m_orientation; } + + // Orthonormal [getXAxis(), getYAxis(), getZAxis()] orientation matrix + inline const float32_t3x3& getOrthonornalMatrix() const { return m_orthonormal; } + + // Base "right" vector in orthonormal orientation basis (X-axis) + inline const float32_t3& getXAxis() const { return m_orthonormal[0u]; } + + // Base "up" vector in orthonormal orientation basis (Y-axis) + inline const float32_t3& getYAxis() const { return m_orthonormal[1u]; } + + // Base "forward" vector in orthonormal orientation basis (Z-axis) + inline const float32_t3& getZAxis() const { return m_orthonormal[2u]; } + + // Optional view of a gimbal + inline const std::optional& getView() const { return m_view; } + + inline const size_t& getManipulationCounter() { return m_counter; } + inline bool isManipulating() const { return m_isManipulating; } + + private: + inline void updateOrthonormalOrientationBase() + { + m_orthonormal = matrix(glm::mat3_cast(glm::normalize(m_orientation))); + } + + inline void updateView() + { + if (m_view.has_value()) // TODO: this could be templated + constexpr actually if gimbal doesn't init this on runtime depending on sth + { + auto& view = m_view.value(); + const auto& gRight = getXAxis(), gUp = getYAxis(), gForward = getZAxis(); + + // TODO: I think I will provide convert utility allowing to go from one hand system to another, its just a matter to take care of m_view->matrix[2u] to perform a LH/RH flip + // in general this should not know about projections which are now supposed to be independent and store reference to a camera (or own it) + view.isLeftHandSystem = hlsl::determinant(m_orthonormal) < 0.0f; + + auto isNormalized = [](const auto& v, float epsilon) -> bool + { + return glm::epsilonEqual(glm::length(v), 1.0f, epsilon); + }; + + auto isOrthogonal = [](const auto& a, const auto& b, float epsilon) -> bool + { + return glm::epsilonEqual(glm::dot(a, b), 0.0f, epsilon); + }; + + auto isOrthoBase = [&](const auto& x, const auto& y, const auto& z, float epsilon = 1e-6f) -> bool + { + return isNormalized(x, epsilon) && isNormalized(y, epsilon) && isNormalized(z, epsilon) && + isOrthogonal(x, y, epsilon) && isOrthogonal(x, z, epsilon) && isOrthogonal(y, z, epsilon); + }; + + assert(isOrthoBase(gRight, gUp, gForward)); + + view.matrix[0u] = vector(gRight, -glm::dot(gRight, m_position)); + view.matrix[1u] = vector(gUp, -glm::dot(gUp, m_position)); + view.matrix[2u] = vector(gForward, -glm::dot(gForward, m_position)); + } + } + + float32_t3 m_position; + glm::quat m_orientation; + matrix m_orthonormal; + + // For a camera implementation at least one gimbal models its view but not all gimbals (if multiple) are expected to do so + std::optional m_view = std::nullopt; + + // Counts *performed* manipulations, a manipulation with 0 delta is not counted! + size_t m_counter = {}; + + // Records manipulation state + bool m_isManipulating = false; + }; + + ICameraController() {} + + // Binds key codes to virtual events, the mapKeys lambda will be executed with controller CVirtualGimbalEvent::keys_to_virtual_events_t table + void updateKeysToEvent(const std::function& mapKeys) + { + mapKeys(m_keysToVirtualEvents); + } + + // Manipulates camera with view gimbal & virtual events + virtual void manipulate(std::span virtualEvents) = 0; + + // TODO: *maybe* would be good to have a class interface for virtual event generators, + // eg keyboard, mouse but maybe custom stuff too eg events from gimbal drag & drop + + // Processes keyboard events to generate virtual manipulation events, note that it doesn't make the manipulation itself! + void processKeyboard(CVirtualGimbalEvent* output, uint32_t& count, std::span events) + { + if (!output) + { + count = CVirtualGimbalEvent::EventsCount; + return; + } + + count = 0u; + + if (events.empty()) + return; + + const auto timestamp = getEventGenerationTimestamp(); + + for (const auto virtualEventType : CVirtualGimbalEvent::VirtualEventsTypeTable) + { + const auto code = m_keysToVirtualEvents[virtualEventType]; + bool& keyDown = m_keysDown[virtualEventType]; + + using virtual_key_state_t = std::tuple; + + auto updateVirtualState = [&]() -> virtual_key_state_t + { + virtual_key_state_t state = { ui::E_KEY_CODE::EKC_NONE, false, 0.f }; + + for (const auto& ev : events) // TODO: improve the search + { + if (ev.keyCode == code) + { + if (ev.action == nbl::ui::SKeyboardEvent::ECA_PRESSED && !keyDown) + keyDown = true; + else if (ev.action == nbl::ui::SKeyboardEvent::ECA_RELEASED) + keyDown = false; + + const auto dt = std::chrono::duration_cast(timestamp - ev.timeStamp).count(); + assert(dt >= 0); + + state = std::make_tuple(code, keyDown, dt); + break; + } + } + + return state; + }; + + const auto&& [physicalKey, isDown, dtAction] = updateVirtualState(); + + if (physicalKey != ui::E_KEY_CODE::EKC_NONE) + if (isDown) + { + auto* virtualEvent = output + count; + assert(virtualEvent); // TODO: maybe just log error and return 0 count + + virtualEvent->type = virtualEventType; + virtualEvent->magnitude = static_cast(dtAction); + ++count; + } + } + } + + // Processes mouse events to generate virtual manipulation events, note that it doesn't make the manipulation itself! + // Limited to Pan & Tilt rotation events, camera type implements how event magnitudes should be interpreted + void processMouse(CVirtualGimbalEvent* output, uint32_t& count, std::span events) + { + if (!output) + { + count = 2u; + return; + } + + count = 0u; + + if (events.empty()) + return; + + const auto timestamp = getEventGenerationTimestamp(); + double dYaw = {}, dPitch = {}; + + for (const auto& ev : events) + if (ev.type == nbl::ui::SMouseEvent::EET_MOVEMENT) + { + dYaw += ev.movementEvent.relativeMovementX; + dPitch += ev.movementEvent.relativeMovementY; + } + + if (dPitch) + { + auto* pitch = output + count; + assert(pitch); // TODO: maybe just log error and return 0 count + pitch->type = dPitch > 0.f ? CVirtualGimbalEvent::TiltUp : CVirtualGimbalEvent::TiltDown; + pitch->magnitude = std::abs(dPitch); + count++; + } + + if (dYaw) + { + auto* yaw = output + count; + assert(yaw); // TODO: maybe just log error and return 0 count + yaw->type = dYaw > 0.f ? CVirtualGimbalEvent::PanRight : CVirtualGimbalEvent::PanLeft; + yaw->magnitude = std::abs(dYaw); + count++; + } + } + +protected: + virtual void initKeysToEvent() = 0; + +private: + CVirtualGimbalEvent::keys_to_virtual_events_t m_keysToVirtualEvents = { { ui::E_KEY_CODE::EKC_NONE } }; + bool m_keysDown[CVirtualGimbalEvent::EventsCount] = {}; + + // exactly what our Nabla events do, actually I don't want users to pass timestamp since I know when it should be best to make a request -> just before generating events! + // TODO: need to think about this + inline std::chrono::microseconds getEventGenerationTimestamp() { return std::chrono::duration_cast(std::chrono::steady_clock::now().time_since_epoch()); } +}; + +#if 0 // TOOD: update +template +concept GimbalRange = GeneralPurposeRange && requires +{ + requires ProjectionMatrix::projection_t>; + requires std::same_as, typename ICameraController::projection_t>::CGimbal>; +}; + +template +class IGimbalRange : public IRange +{ +public: + using base_t = IRange; + using range_t = typename base_t::range_t; + using gimbal_t = typename base_t::range_value_t; + + IGimbalRange(range_t&& gimbals) : base_t(std::move(gimbals)) {} + inline const range_t& getGimbals() const { return base_t::m_range; } + +protected: + inline range_t& getGimbals() const { return base_t::m_range; } +}; + +// TODO NOTE: eg. "follow camera" should use GimbalRange::CGimbal, 2u>>, +// one per camera itself and one for target it follows +#endif + +} // nbl::hlsl namespace + +#endif // _NBL_I_CAMERA_CONTROLLER_HPP_ \ No newline at end of file diff --git a/common/include/camera/ILinearProjection.hpp b/common/include/camera/ILinearProjection.hpp new file mode 100644 index 000000000..30a4db358 --- /dev/null +++ b/common/include/camera/ILinearProjection.hpp @@ -0,0 +1,29 @@ +#ifndef _NBL_ILINEAR_PROJECTION_HPP_ +#define _NBL_ILINEAR_PROJECTION_HPP_ + +#include "IProjection.hpp" + +namespace nbl::hlsl +{ + +//! Interface class for linear projections range storage - a projection matrix represents single view-port +template +class ILinearProjection : protected IProjectionRange +{ +public: + using base_t = typename IProjectionRange; + using range_t = typename base_t::range_t; + using projection_t = typename base_t::projection_t; + + ILinearProjection(range_t&& projections) : base_t(std::move(projections)) {} + +protected: + inline range_t& getViewportProjections() + { + return base_t::m_projectionRange; + } +}; + +} // nbl::hlsl namespace + +#endif // _NBL_ILINEAR_PROJECTION_HPP_ \ No newline at end of file diff --git a/common/include/camera/IProjection.hpp b/common/include/camera/IProjection.hpp new file mode 100644 index 000000000..8de5c19f6 --- /dev/null +++ b/common/include/camera/IProjection.hpp @@ -0,0 +1,72 @@ +#ifndef _NBL_IPROJECTION_HPP_ +#define _NBL_IPROJECTION_HPP_ + +#include "nbl/builtin/hlsl/cpp_compat/matrix.hlsl" +#include "IRange.hpp" + +namespace nbl::hlsl +{ + +template +concept ProjectionMatrix = is_any_of_v; + +//! Interface class for projection +template +class IProjection : virtual public core::IReferenceCounted +{ +public: + using value_t = T; + + IProjection(const value_t& matrix = {}) : m_projectionMatrix(matrix) { updateHandnessState(); } + + inline void setMatrix(const value_t& projectionMatrix) + { + m_projectionMatrix = projectionMatrix; + updateHandnessState(); + } + + inline const value_t& getMatrix() { return m_projectionMatrix; } + inline bool isLeftHanded() { return m_isLeftHanded; } + +private: + inline void updateHandnessState() + { + m_isLeftHanded = hlsl::determinant(m_projectionMatrix) < 0.f; + } + + value_t m_projectionMatrix; + bool m_isLeftHanded; +}; + +template +struct is_projection : std::false_type {}; + +template +struct is_projection> : std::true_type {}; + +template +inline constexpr bool is_projection_v = is_projection::value; + +template +concept ProjectionRange = GeneralPurposeRange && requires +{ + requires core::is_smart_refctd_ptr_v>; + requires is_projection_v::pointee>; +}; + +//! Interface class for a range of IProjection projections +template>, 1u>> +class IProjectionRange : public IRange +{ +public: + using base_t = IRange; + using range_t = typename base_t::range_t; + using projection_t = typename base_t::range_value_t; + + IProjectionRange(range_t&& projections) : base_t(std::move(projections)) {} + const range_t& getProjections() const { return base_t::m_range; } +}; + +} // namespace nbl::hlsl + +#endif // _NBL_IPROJECTION_HPP_ \ No newline at end of file