From 6fd27d7f416ccf7701fcfa53e1975660eda2daf7 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Thu, 16 Jul 2026 16:50:48 +0700 Subject: [PATCH 1/8] set up global scan test shader --- 78_ChainedScanUnitTest/CMakeLists.txt | 14 + .../app_resources/common.hlsl | 97 ++++ .../app_resources/shaderCommon.hlsl | 19 + .../app_resources/testGlobal.comp.hlsl | 166 ++++++ 78_ChainedScanUnitTest/main.cpp | 509 ++++++++++++++++++ CMakeLists.txt | 1 + 6 files changed, 806 insertions(+) create mode 100644 78_ChainedScanUnitTest/CMakeLists.txt create mode 100644 78_ChainedScanUnitTest/app_resources/common.hlsl create mode 100644 78_ChainedScanUnitTest/app_resources/shaderCommon.hlsl create mode 100644 78_ChainedScanUnitTest/app_resources/testGlobal.comp.hlsl create mode 100644 78_ChainedScanUnitTest/main.cpp diff --git a/78_ChainedScanUnitTest/CMakeLists.txt b/78_ChainedScanUnitTest/CMakeLists.txt new file mode 100644 index 000000000..a1935bec0 --- /dev/null +++ b/78_ChainedScanUnitTest/CMakeLists.txt @@ -0,0 +1,14 @@ +include(common) + +nbl_create_executable_project("" "" "" "") + +NBL_CREATE_RESOURCE_ARCHIVE( + NAMESPACE nbl::this_example::builtin + TARGET ${EXECUTABLE_NAME}_builtins + LINK_TO ${EXECUTABLE_NAME} + BIND app_resources + BUILTINS + common.hlsl + shaderCommon.hlsl + testGlobal.comp.hlsl +) \ No newline at end of file diff --git a/78_ChainedScanUnitTest/app_resources/common.hlsl b/78_ChainedScanUnitTest/app_resources/common.hlsl new file mode 100644 index 000000000..5465dc6fa --- /dev/null +++ b/78_ChainedScanUnitTest/app_resources/common.hlsl @@ -0,0 +1,97 @@ +#include "nbl/builtin/hlsl/cpp_compat.hlsl" +#include "nbl/builtin/hlsl/functional.hlsl" + +struct PushConstantData +{ + uint64_t pInputBuf; + uint64_t pOutputBuf[8]; + uint64_t pReduceBuf; +}; + +namespace arithmetic +{ +// Thanks to our unified HLSL/C++ STD lib we're able to remove a whole load of code +template +struct bit_and : nbl::hlsl::bit_and +{ + using base_t = nbl::hlsl::bit_and; + + NBL_CONSTEXPR_STATIC_INLINE uint16_t BindingIndex = 0; +#ifndef __HLSL_VERSION + static inline constexpr const char* name = "bit_and"; +#endif +}; +template +struct bit_or : nbl::hlsl::bit_or +{ + using base_t = nbl::hlsl::bit_or; + + NBL_CONSTEXPR_STATIC_INLINE uint16_t BindingIndex = 1; +#ifndef __HLSL_VERSION + static inline constexpr const char* name = "bit_xor"; +#endif +}; +template +struct bit_xor : nbl::hlsl::bit_xor +{ + using base_t = nbl::hlsl::bit_xor; + + NBL_CONSTEXPR_STATIC_INLINE uint16_t BindingIndex = 2; +#ifndef __HLSL_VERSION + static inline constexpr const char* name = "bit_or"; +#endif +}; +template +struct plus : nbl::hlsl::plus +{ + using base_t = nbl::hlsl::plus; + + NBL_CONSTEXPR_STATIC_INLINE uint16_t BindingIndex = 3; +#ifndef __HLSL_VERSION + static inline constexpr const char* name = "plus"; +#endif +}; +template +struct multiplies : nbl::hlsl::multiplies +{ + using base_t = nbl::hlsl::multiplies; + + NBL_CONSTEXPR_STATIC_INLINE uint16_t BindingIndex = 4; +#ifndef __HLSL_VERSION + static inline constexpr const char* name = "multiplies"; +#endif +}; +template +struct minimum : nbl::hlsl::minimum +{ + using base_t = nbl::hlsl::minimum; + + NBL_CONSTEXPR_STATIC_INLINE uint16_t BindingIndex = 5; +#ifndef __HLSL_VERSION + static inline constexpr const char* name = "minimum"; +#endif +}; +template +struct maximum : nbl::hlsl::maximum +{ + using base_t = nbl::hlsl::maximum; + + NBL_CONSTEXPR_STATIC_INLINE uint16_t BindingIndex = 6; +#ifndef __HLSL_VERSION + static inline constexpr const char* name = "maximum"; +#endif +}; + +template +struct ballot : nbl::hlsl::plus +{ + using base_t = nbl::hlsl::plus; + + NBL_CONSTEXPR_STATIC_INLINE uint16_t BindingIndex = 7; +#ifndef __HLSL_VERSION + static inline constexpr const char* name = "bitcount"; +#endif +}; +} + +#include "nbl/builtin/hlsl/glsl_compat/subgroup_basic.hlsl" diff --git a/78_ChainedScanUnitTest/app_resources/shaderCommon.hlsl b/78_ChainedScanUnitTest/app_resources/shaderCommon.hlsl new file mode 100644 index 000000000..5baf9a28d --- /dev/null +++ b/78_ChainedScanUnitTest/app_resources/shaderCommon.hlsl @@ -0,0 +1,19 @@ +#include "app_resources/common.hlsl" + +using namespace nbl; +using namespace hlsl; + +[[vk::push_constant]] PushConstantData pc; + +struct device_capabilities +{ +#ifdef TEST_NATIVE + NBL_CONSTEXPR_STATIC_INLINE bool shaderSubgroupArithmetic = true; +#else + NBL_CONSTEXPR_STATIC_INLINE bool shaderSubgroupArithmetic = false; +#endif +}; + +#ifndef OPERATION +#error "Define OPERATION!" +#endif diff --git a/78_ChainedScanUnitTest/app_resources/testGlobal.comp.hlsl b/78_ChainedScanUnitTest/app_resources/testGlobal.comp.hlsl new file mode 100644 index 000000000..e6aa6cbde --- /dev/null +++ b/78_ChainedScanUnitTest/app_resources/testGlobal.comp.hlsl @@ -0,0 +1,166 @@ + #pragma shader_stage(compute) + +#define operation_t nbl::hlsl::OPERATION + +#include "nbl/builtin/hlsl/glsl_compat/core.hlsl" +#include "nbl/builtin/hlsl/glsl_compat/subgroup_basic.hlsl" +#include "nbl/builtin/hlsl/subgroup2/arithmetic_portability.hlsl" +#include "nbl/builtin/hlsl/subgroup2/arithmetic_params.hlsl" + +#include "nbl/builtin/hlsl/scan/chained_scan.hlsl" + +using config_t = WORKGROUP_CONFIG_T; + +#include "app_resources/shaderCommon.hlsl" + +typedef vector type_t; + +groupshared uint32_t scratch[mpl::max_v]; + +struct ScratchProxy +{ + template + void get(const uint32_t ix, NBL_REF_ARG(AccessType) value) + { + value = scratch[ix]; + } + template + void set(const uint32_t ix, const AccessType value) + { + scratch[ix] = value; + } + + uint32_t atomicOr(const uint32_t ix, const uint32_t value) + { + return glsl::atomicOr(scratch[ix],value); + } + + void workgroupExecutionAndMemoryBarrier() + { + glsl::barrier(); + } +}; + +template +struct DataProxy +{ + using dtype_t = vector; + // function template AccessType should be the same as dtype_t + + static DataProxy create(const uint64_t inputBuf, const uint64_t outputBuf) + { + DataProxy retval; + const uint32_t workgroupOffset = glsl::gl_WorkGroupID().x * VirtualWorkgroupSize * sizeof(dtype_t); + retval.accessor = DoubleLegacyBdaAccessor::create(inputBuf + workgroupOffset, outputBuf + workgroupOffset); + return retval; + } + + template + void get(const IndexType ix, NBL_REF_ARG(AccessType) value) + { + accessor.get(ix, value); + } + template + void set(const IndexType ix, const AccessType value) + { + accessor.set(ix, value); + } + + uint64_t getInputBufAddr() + { + return accessor.inputAddress; + } + uint64_t getOutputBufAddr() + { + return accessor.outputAddress; + } + + void workgroupExecutionAndMemoryBarrier() + { + glsl::barrier(); + //glsl::memoryBarrierShared(); implied by the above + } + + DoubleLegacyBdaAccessor accessor; +}; + +template +struct ReduceAccessor +{ + using type_t = T; + static ReduceAccessor create(const bda::__ptr ptr) + { + ReduceAccessor retval; + retval.ptr = ptr; + return retval; + } + + void get(const uint64_t index, NBL_REF_ARG(T) value) + { + bda::__ptr target = ptr + index; + value = target.template deref().load(); + } + void set(const uint64_t index, const T value) + { + bda::__ptr target = ptr + index; + return target.template deref().store(value); + } + + T atomicExchange(const uint64_t index, const T value) + { + bda::__ptr target = ptr + index; + return glsl::atomicExchange(target.template deref().ptr.value, value); + } + + bda::__ptr ptr; +}; + +static ScratchProxy arithmeticAccessor; + +template +struct operation_t +{ + using binop_base_t = typename Binop::base_t; + using otype_t = typename Binop::type_t; + + void operator()() + { + using data_proxy_t = DataProxy; + data_proxy_t dataAccessor = data_proxy_t::create(pc.pInputBuf, pc.pOutputBuf[Binop::BindingIndex]); + + using reduce_proxy_t = ReduceAccessor; + bda::__ptr ptr = bda::__ptr::create(pc.pReduceBuf); + reduce_proxy_t reduceAccessor = reduce_proxy_t::create(ptr); + + OPERATION::template __call(dataAccessor,arithmeticAccessor,reduceAccessor); + // we barrier before because we alias the accessors for Binop + arithmeticAccessor.workgroupExecutionAndMemoryBarrier(); + } +}; + + +template +static void subtest() +{ + assert(glsl::gl_SubgroupSize() == config_t::SubgroupSize) + + operation_t func; + func(); +} + +void test() +{ + subtest >(); + subtest >(); + subtest >(); + subtest >(); + subtest >(); + subtest >(); + subtest >(); +} + +[numthreads(WORKGROUP_SIZE,1,1)] +void main() +{ + test(); +} diff --git a/78_ChainedScanUnitTest/main.cpp b/78_ChainedScanUnitTest/main.cpp new file mode 100644 index 000000000..2fa978953 --- /dev/null +++ b/78_ChainedScanUnitTest/main.cpp @@ -0,0 +1,509 @@ +// TODO: copyright notice + + +#include "nbl/examples/examples.hpp" + +#include "app_resources/common.hlsl" +#include "nbl/builtin/hlsl/workgroup2/arithmetic_config.hlsl" +#include "nbl/builtin/hlsl/subgroup2/arithmetic_params.hlsl" + + +using namespace nbl; +using namespace core; +using namespace asset; +using namespace system; +using namespace video; + +// method emulations on the CPU, to verify the results of the GPU methods +template +struct emulatedReduction +{ + using type_t = typename Binop::type_t; + + static inline void impl(type_t* out, const type_t* in, const uint32_t itemCount) + { + const type_t red = std::reduce(in,in+itemCount,Binop::identity,Binop()); + std::fill(out,out+itemCount,red); + } + + static inline constexpr const char* name = "reduction"; +}; +template +struct emulatedScanInclusive +{ + using type_t = typename Binop::type_t; + + static inline void impl(type_t* out, const type_t* in, const uint32_t itemCount) + { + std::inclusive_scan(in,in+itemCount,out,Binop()); + } + static inline constexpr const char* name = "inclusive_scan"; +}; +template +struct emulatedScanExclusive +{ + using type_t = typename Binop::type_t; + + static inline void impl(type_t* out, const type_t* in, const uint32_t itemCount) + { + std::exclusive_scan(in,in+itemCount,out,Binop::identity,Binop()); + } + static inline constexpr const char* name = "exclusive_scan"; +}; + +class Workgroup2ScanTestApp final : public application_templates::BasicMultiQueueApplication, public examples::BuiltinResourcesApplication +{ + using device_base_t = application_templates::BasicMultiQueueApplication; + using asset_base_t = examples::BuiltinResourcesApplication; + +public: + Workgroup2ScanTestApp(const path& _localInputCWD, const path& _localOutputCWD, const path& _sharedInputCWD, const path& _sharedOutputCWD) : + system::IApplicationFramework(_localInputCWD, _localOutputCWD, _sharedInputCWD, _sharedOutputCWD) {} + + bool onAppInitialized(smart_refctd_ptr&& system) override + { + if (!device_base_t::onAppInitialized(std::move(system))) + return false; + if (!asset_base_t::onAppInitialized(std::move(system))) + return false; + + transferDownQueue = getTransferDownQueue(); + computeQueue = getComputeQueue(); + + // TODO: get the element count from argv + const uint32_t elementCount = 1024 * 1024; + // populate our random data buffer on the CPU and create a GPU copy + inputData = new uint32_t[elementCount]; + smart_refctd_ptr gpuinputDataBuffer; + { + std::mt19937 randGenerator(0xdeadbeefu); + for (uint32_t i = 0u; i < elementCount; i++) + inputData[i] = randGenerator(); // TODO: change to using xoroshiro, then we can skip having the input buffer at all + + IGPUBuffer::SCreationParams inputDataBufferCreationParams = {}; + inputDataBufferCreationParams.size = sizeof(uint32_t) * elementCount; + inputDataBufferCreationParams.usage = IGPUBuffer::EUF_STORAGE_BUFFER_BIT | IGPUBuffer::EUF_TRANSFER_DST_BIT | IGPUBuffer::EUF_SHADER_DEVICE_ADDRESS_BIT; + m_utils->createFilledDeviceLocalBufferOnDedMem( + SIntendedSubmitInfo{.queue=getTransferUpQueue()}, + std::move(inputDataBufferCreationParams), + inputData + ).move_into(gpuinputDataBuffer); + } + + // create 8 buffers for 8 operations + for (auto i=0u; igetSize(); + params.usage = bitflag(IGPUBuffer::EUF_STORAGE_BUFFER_BIT) | IGPUBuffer::EUF_TRANSFER_SRC_BIT | IGPUBuffer::EUF_SHADER_DEVICE_ADDRESS_BIT; + + outputBuffers[i] = m_device->createBuffer(std::move(params)); + auto mreq = outputBuffers[i]->getMemoryReqs(); + mreq.memoryTypeBits &= m_physicalDevice->getDeviceLocalMemoryTypeBits(); + assert(mreq.memoryTypeBits); + + auto bufferMem = m_device->allocate(mreq, { outputBuffers[i].get(), IDeviceMemoryAllocation::EMAF_DEVICE_ADDRESS_BIT }); + assert(bufferMem.isValid()); + } + pc.pInputBuf = gpuinputDataBuffer->getDeviceAddress(); + for (uint32_t i = 0; i < OutputBufferCount; i++) + pc.pOutputBuf[i] = outputBuffers[i]->getDeviceAddress(); + + // create Pipeline Layout + { + SPushConstantRange pcRange = { .stageFlags = IShader::E_SHADER_STAGE::ESS_COMPUTE, .offset = 0,.size = sizeof(PushConstantData) }; + pipelineLayout = m_device->createPipelineLayout({&pcRange, 1}); + } + + const auto spirv_isa_cache_path = localOutputCWD / "spirv_isa_cache.bin"; + // enclose to make sure file goes out of scope and we can reopen it + { + smart_refctd_ptr spirv_isa_cache_input; + // try to load SPIR-V to ISA cache + { + ISystem::future_t> fileCreate; + m_system->createFile(fileCreate, spirv_isa_cache_path, IFile::ECF_READ | IFile::ECF_MAPPABLE | IFile::ECF_COHERENT); + if (auto lock = fileCreate.acquire()) + spirv_isa_cache_input = *lock; + } + // create the cache + { + std::span spirv_isa_cache_data = {}; + if (spirv_isa_cache_input) + spirv_isa_cache_data = { reinterpret_cast(spirv_isa_cache_input->getMappedPointer()),spirv_isa_cache_input->getSize() }; + else + m_logger->log("Failed to load SPIR-V 2 ISA cache!", ILogger::ELL_PERFORMANCE); + // Normally we'd deserialize a `ICPUPipelineCache` properly and pass that instead + m_spirv_isa_cache = m_device->createPipelineCache(spirv_isa_cache_data); + } + } + { + // TODO: rename `deleteDirectory` to just `delete`? and a `IFile::setSize()` ? + m_system->deleteDirectory(spirv_isa_cache_path); + ISystem::future_t> fileCreate; + m_system->createFile(fileCreate, spirv_isa_cache_path, IFile::ECF_WRITE); + // I can be relatively sure I'll succeed to acquire the future, the pointer to created file might be null though. + m_spirv_isa_cache_output = *fileCreate.acquire(); + if (!m_spirv_isa_cache_output) + logFail("Failed to Create SPIR-V to ISA cache file."); + } + + // load shader source from file + auto getShaderSource = [&](const char* filePath) -> auto + { + IAssetLoader::SAssetLoadParams lparams = {}; + lparams.logger = m_logger.get(); + lparams.workingDirectory = ""; + auto bundle = m_assetMgr->getAsset(filePath, lparams); + if (bundle.getContents().empty() || bundle.getAssetType()!=IAsset::ET_SHADER) + { + m_logger->log("Shader %s not found!", ILogger::ELL_ERROR, filePath); + exit(-1); + } + auto firstAssetInBundle = bundle.getContents()[0]; + return smart_refctd_ptr_static_cast(firstAssetInBundle); + }; + + auto subgroupTestSource = getShaderSource("app_resources/testSubgroup.comp.hlsl"); + auto workgroupTestSource = getShaderSource("app_resources/testWorkgroup.comp.hlsl"); + // now create or retrieve final resources to run our tests + sema = m_device->createSemaphore(timelineValue); + resultsBuffer = ICPUBuffer::create({ outputBuffers[0]->getSize() }); + { + smart_refctd_ptr cmdpool = m_device->createCommandPool(computeQueue->getFamilyIndex(),IGPUCommandPool::CREATE_FLAGS::RESET_COMMAND_BUFFER_BIT); + if (!cmdpool->createCommandBuffers(IGPUCommandPool::BUFFER_LEVEL::PRIMARY,{&cmdbuf,1})) + { + logFail("Failed to create Command Buffers!\n"); + return false; + } + } + + const auto MaxWorkgroupSize = m_physicalDevice->getLimits().maxComputeWorkGroupInvocations; + const auto MinSubgroupSize = m_physicalDevice->getLimits().minSubgroupSize; + const auto MaxSubgroupSize = m_physicalDevice->getLimits().maxSubgroupSize; + for (uint32_t useNative = 0; useNative <= uint32_t(m_physicalDevice->getProperties().limits.shaderSubgroupArithmetic); useNative++) + { + if (useNative) + m_logger->log("Testing with native subgroup arithmetic", ILogger::ELL_INFO); + else + m_logger->log("Testing with emulated subgroup arithmetic", ILogger::ELL_INFO); + + for (auto subgroupSize = MinSubgroupSize; subgroupSize <= MaxSubgroupSize; subgroupSize *= 2u) + { + const uint8_t subgroupSizeLog2 = hlsl::findMSB(subgroupSize); + for (uint32_t workgroupSize = subgroupSize; workgroupSize <= MaxWorkgroupSize; workgroupSize *= 2u) + { + // make sure renderdoc captures everything for debugging + m_api->startCapture(); + m_logger->log("Testing Workgroup Size %u with Subgroup Size %u", ILogger::ELL_INFO, workgroupSize, subgroupSize); + + for (uint32_t j = 0; j < ItemsPerInvocations.size(); j++) + { + const uint32_t itemsPerInvocation = ItemsPerInvocations[j]; + uint32_t itemsPerWG = workgroupSize * itemsPerInvocation; + m_logger->log("Testing Items per Invocation %u", ILogger::ELL_INFO, itemsPerInvocation); + bool passed = true; + passed = runTest(subgroupTestSource, elementCount, subgroupSizeLog2, workgroupSize, bool(useNative), itemsPerWG, itemsPerInvocation) && passed; + logTestOutcome(passed, itemsPerWG); + passed = runTest(subgroupTestSource, elementCount, subgroupSizeLog2, workgroupSize, bool(useNative), itemsPerWG, itemsPerInvocation) && passed; + logTestOutcome(passed, itemsPerWG); + passed = runTest(subgroupTestSource, elementCount, subgroupSizeLog2, workgroupSize, bool(useNative), itemsPerWG, itemsPerInvocation) && passed; + logTestOutcome(passed, itemsPerWG); + + hlsl::workgroup2::SArithmeticConfiguration wgConfig; + wgConfig.init(hlsl::findMSB(workgroupSize), subgroupSizeLog2, itemsPerInvocation); + itemsPerWG = wgConfig.VirtualWorkgroupSize * wgConfig.ItemsPerInvocation_0; + m_logger->log("Testing Item Count %u", ILogger::ELL_INFO, itemsPerWG); + passed = runTest(workgroupTestSource, elementCount, subgroupSizeLog2, workgroupSize, bool(useNative), itemsPerWG, itemsPerInvocation) && passed; + logTestOutcome(passed, itemsPerWG); + passed = runTest(workgroupTestSource, elementCount, subgroupSizeLog2, workgroupSize, bool(useNative), itemsPerWG, itemsPerInvocation) && passed; + logTestOutcome(passed, itemsPerWG); + passed = runTest(workgroupTestSource, elementCount, subgroupSizeLog2, workgroupSize, bool(useNative), itemsPerWG, itemsPerInvocation) && passed; + logTestOutcome(passed, itemsPerWG); + } + m_api->endCapture(); + + // save cache every now and then + { + auto cpu = m_spirv_isa_cache->convertToCPUCache(); + // Normally we'd beautifully JSON serialize the thing, allow multiple devices & drivers + metadata + auto bin = cpu->getEntries().begin()->second.bin; + IFile::success_t success; + m_spirv_isa_cache_output->write(success, bin->data(), 0ull, bin->size()); + if (!success) + logFail("Could not write Create SPIR-V to ISA cache to disk!"); + } + } + } + } + + return true; + } + + virtual bool onAppTerminated() override + { + m_logger->log("==========Result==========", ILogger::ELL_INFO); + m_logger->log("Fail Count: %u", ILogger::ELL_INFO, totalFailCount); + delete[] inputData; + return true; + } + + // the unit test is carried out on init + void workLoopBody() override {} + + // + bool keepRunning() override { return false; } + +private: + void logTestOutcome(bool passed, uint32_t workgroupSize) + { + if (passed) + m_logger->log("Passed test #%u", ILogger::ELL_INFO, workgroupSize); + else + { + totalFailCount++; + m_logger->log("Failed test #%u", ILogger::ELL_ERROR, workgroupSize); + } + } + + // create pipeline (specialized every test) [TODO: turn into a future/async] + smart_refctd_ptr createPipeline(const IShader* overridenUnspecialized, const uint8_t subgroupSizeLog2) + { + auto shader = m_device->compileShader({ overridenUnspecialized }); + IGPUComputePipeline::SCreationParams params = {}; + params.layout = pipelineLayout.get(); + params.shader = { + .shader = shader.get(), + .entryPoint = "main", + .requiredSubgroupSize = static_cast(subgroupSizeLog2), + .entries = nullptr, + }; + params.cached.requireFullSubgroups = true; + core::smart_refctd_ptr pipeline; + if (!m_device->createComputePipelines(m_spirv_isa_cache.get(),{¶ms,1},&pipeline)) + return nullptr; + return pipeline; + } + + template class Arithmetic, bool WorkgroupTest> + bool runTest(const smart_refctd_ptr& source, const uint32_t elementCount, const uint8_t subgroupSizeLog2, const uint32_t workgroupSize, bool useNative, uint32_t itemsPerWG, uint32_t itemsPerInvoc = 1u) + { + std::string arith_name = Arithmetic>::name; + const uint32_t workgroupSizeLog2 = hlsl::findMSB(workgroupSize); + + auto compiler = make_smart_refctd_ptr(smart_refctd_ptr(m_system)); + CHLSLCompiler::SOptions options = {}; + options.stage = IShader::E_SHADER_STAGE::ESS_COMPUTE; + options.preprocessorOptions.targetSpirvVersion = m_device->getPhysicalDevice()->getLimits().spirvVersion; + options.spirvOptimizer = nullptr; +#ifndef _NBL_DEBUG + ISPIRVOptimizer::E_OPTIMIZER_PASS optPasses = ISPIRVOptimizer::EOP_STRIP_DEBUG_INFO; + auto opt = make_smart_refctd_ptr(std::span(&optPasses, 1)); + options.spirvOptimizer = opt.get(); +#else + options.debugInfoFlags |= IShaderCompiler::E_DEBUG_INFO_FLAGS::EDIF_LINE_BIT; +#endif + options.preprocessorOptions.sourceIdentifier = source->getFilepathHint(); + options.preprocessorOptions.logger = m_logger.get(); + + auto* includeFinder = compiler->getDefaultIncludeFinder(); + options.preprocessorOptions.includeFinder = includeFinder; + + smart_refctd_ptr overriddenUnspecialized; + if constexpr (WorkgroupTest) + { + hlsl::workgroup2::SArithmeticConfiguration wgConfig; + wgConfig.init(hlsl::findMSB(workgroupSize), subgroupSizeLog2, itemsPerInvoc); + + const std::string definitions[3] = { + "workgroup2::" + arith_name, + wgConfig.getConfigTemplateStructString(), + std::to_string(arith_name=="reduction") + }; + + const IShaderCompiler::SMacroDefinition defines[4] = { + { "OPERATION", definitions[0] }, + { "WORKGROUP_CONFIG_T", definitions[1] }, + { "IS_REDUCTION", definitions[2] }, + { "TEST_NATIVE", "1" } + }; + if (useNative) + options.preprocessorOptions.extraDefines = { defines, defines + 4 }; + else + options.preprocessorOptions.extraDefines = { defines, defines + 3 }; + + overriddenUnspecialized = compiler->compileToSPIRV((const char*)source->getContent()->getPointer(), options); + } + else + { + hlsl::subgroup2::SArithmeticParams sgParams; + sgParams.init(subgroupSizeLog2, itemsPerInvoc); + + const std::string definitions[3] = { + "subgroup2::" + arith_name, + std::to_string(workgroupSize), + sgParams.getParamTemplateStructString() + }; + + const IShaderCompiler::SMacroDefinition defines[4] = { + { "OPERATION", definitions[0] }, + { "WORKGROUP_SIZE", definitions[1] }, + { "SUBGROUP_CONFIG_T", definitions[2] }, + { "TEST_NATIVE", "1" } + }; + if (useNative) + options.preprocessorOptions.extraDefines = { defines, defines + 4 }; + else + options.preprocessorOptions.extraDefines = { defines, defines + 3 }; + + overriddenUnspecialized = compiler->compileToSPIRV((const char*)source->getContent()->getPointer(), options); + } + + auto pipeline = createPipeline(overriddenUnspecialized.get(),subgroupSizeLog2); + + // TODO: overlap dispatches with memory readbacks (requires multiple copies of `buffers`) + uint32_t workgroupCount = 1;// min(elementCount / itemsPerWG, m_physicalDevice->getLimits().maxComputeWorkGroupCount[0]); + + cmdbuf->begin(IGPUCommandBuffer::USAGE::NONE); + cmdbuf->bindComputePipeline(pipeline.get()); + cmdbuf->pushConstants(pipelineLayout.get(), IShader::E_SHADER_STAGE::ESS_COMPUTE, 0, sizeof(PushConstantData), &pc); + cmdbuf->dispatch(workgroupCount, 1, 1); + { + IGPUCommandBuffer::SPipelineBarrierDependencyInfo::buffer_barrier_t memoryBarrier[OutputBufferCount]; + for (auto i=0u; igetSize(),outputBuffers[i]} + }; + } + IGPUCommandBuffer::SPipelineBarrierDependencyInfo info = {.memBarriers={},.bufBarriers=memoryBarrier}; + cmdbuf->pipelineBarrier(asset::E_DEPENDENCY_FLAGS::EDF_NONE,info); + } + cmdbuf->end(); + + const IQueue::SSubmitInfo::SSemaphoreInfo signal[1] = {{.semaphore=sema.get(),.value=++timelineValue}}; + const IQueue::SSubmitInfo::SCommandBufferInfo cmdbufs[1] = {{.cmdbuf=cmdbuf.get()}}; + const IQueue::SSubmitInfo submits[1] = {{.commandBuffers=cmdbufs,.signalSemaphores=signal}}; + computeQueue->submit(submits); + const ISemaphore::SWaitInfo wait[1] = {{.semaphore=sema.get(),.value=timelineValue}}; + m_device->blockForSemaphores(wait); + + const uint32_t subgroupSize = 1u << subgroupSizeLog2; + // check results + bool passed = validateResults, WorkgroupTest>(itemsPerWG, workgroupCount, subgroupSize, itemsPerInvoc); + passed = validateResults, WorkgroupTest>(itemsPerWG, workgroupCount, subgroupSize, itemsPerInvoc) && passed; + passed = validateResults, WorkgroupTest>(itemsPerWG, workgroupCount, subgroupSize, itemsPerInvoc) && passed; + passed = validateResults, WorkgroupTest>(itemsPerWG, workgroupCount, subgroupSize, itemsPerInvoc) && passed; + passed = validateResults, WorkgroupTest>(itemsPerWG, workgroupCount, subgroupSize, itemsPerInvoc) && passed; + passed = validateResults, WorkgroupTest>(itemsPerWG, workgroupCount, subgroupSize, itemsPerInvoc) && passed; + passed = validateResults, WorkgroupTest>(itemsPerWG, workgroupCount, subgroupSize, itemsPerInvoc) && passed; + + return passed; + } + + //returns true if result matches + template class Arithmetic, class Binop, bool WorkgroupTest> + bool validateResults(const uint32_t itemsPerWG, const uint32_t workgroupCount, const uint32_t subgroupSize, const uint32_t itemsPerInvoc) + { + bool success = true; + + // download data + const SBufferRange bufferRange = {0u, resultsBuffer->getSize(), outputBuffers[Binop::BindingIndex]}; + m_utils->downloadBufferRangeViaStagingBufferAutoSubmit(SIntendedSubmitInfo{.queue=transferDownQueue},bufferRange,resultsBuffer->getPointer()); + + using type_t = typename Binop::type_t; + const auto testData = reinterpret_cast(resultsBuffer->getPointer()); + + // TODO: parallel for (the temporary values need to be threadlocal or what?) + // now check if the data obtained has valid values + type_t* tmp = new type_t[itemsPerWG]; + for (uint32_t workgroupID = 0u; success && workgroupID < workgroupCount; workgroupID++) + { + if constexpr (WorkgroupTest) + { + const auto workgroupOffset = workgroupID * itemsPerWG; + Arithmetic::impl(tmp, inputData + workgroupOffset, itemsPerWG); + + for (uint32_t localInvocationIndex = 0u; localInvocationIndex < itemsPerWG; localInvocationIndex++) + { + const auto globalInvocationIndex = workgroupOffset + localInvocationIndex; + const auto cpuVal = tmp[localInvocationIndex]; + const auto gpuVal = testData[globalInvocationIndex]; + if (cpuVal != gpuVal) + { + m_logger->log( + "Failed test #%d (%s) (%s) Expected %u got %u for workgroup %d and localinvoc %d", + ILogger::ELL_ERROR, itemsPerWG, WorkgroupTest ? "workgroup" : "subgroup", Binop::name, + cpuVal, gpuVal, workgroupID, localInvocationIndex + ); + success = false; + break; + } + } + } + else + { + const auto workgroupOffset = workgroupID * itemsPerWG; + const auto workgroupSize = itemsPerWG / itemsPerInvoc; + for (uint32_t pseudoSubgroupID = 0u; pseudoSubgroupID < workgroupSize; pseudoSubgroupID += subgroupSize) + Arithmetic::impl(tmp + pseudoSubgroupID * itemsPerInvoc, inputData + workgroupOffset + pseudoSubgroupID * itemsPerInvoc, subgroupSize * itemsPerInvoc); + + for (uint32_t localInvocationIndex = 0u; localInvocationIndex < workgroupSize; localInvocationIndex++) + { + const auto localOffset = localInvocationIndex * itemsPerInvoc; + const auto globalInvocationIndex = workgroupOffset + localOffset; + + for (uint32_t itemInvocationIndex = 0u; itemInvocationIndex < itemsPerInvoc; itemInvocationIndex++) + { + const auto cpuVal = tmp[localOffset + itemInvocationIndex]; + const auto gpuVal = testData[globalInvocationIndex + itemInvocationIndex]; + if (cpuVal != gpuVal) + { + m_logger->log( + "Failed test #%d (%s) (%s) Expected %u got %u for workgroup %d and localinvoc %d and iteminvoc %d", + ILogger::ELL_ERROR, itemsPerWG, WorkgroupTest ? "workgroup" : "subgroup", Binop::name, + cpuVal, gpuVal, workgroupID, localInvocationIndex, itemInvocationIndex + ); + success = false; + break; + } + } + } + } + } + delete[] tmp; + + return success; + } + + IQueue* transferDownQueue; + IQueue* computeQueue; + smart_refctd_ptr m_spirv_isa_cache; + smart_refctd_ptr m_spirv_isa_cache_output; + + uint32_t* inputData = nullptr; + constexpr static inline uint32_t OutputBufferCount = 8u; + smart_refctd_ptr outputBuffers[OutputBufferCount]; + smart_refctd_ptr pipelineLayout; + PushConstantData pc; + + smart_refctd_ptr sema; + uint64_t timelineValue = 0; + smart_refctd_ptr cmdbuf; + smart_refctd_ptr resultsBuffer; + + uint32_t totalFailCount = 0; + + constexpr static inline std::array ItemsPerInvocations = { 1, 2, 3, 4 }; +}; + +NBL_MAIN_FUNC(Workgroup2ScanTestApp) \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index ca7794ca8..d4e2c5a72 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -115,6 +115,7 @@ if(NBL_BUILD_EXAMPLES) add_subdirectory(76_CudaInterop) endif() + add_subdirectory(78_ChainedScanUnitTest) # add new examples *before* NBL_GET_ALL_TARGETS invocation, it gathers recursively all targets created so far in this subdirectory NBL_GET_ALL_TARGETS(TARGETS) From 137ed56fe38065987507f023ba272c4a5d412404 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Fri, 17 Jul 2026 16:47:08 +0700 Subject: [PATCH 2/8] set up test on cpp side, fix some bugs in shader --- .../app_resources/testGlobal.comp.hlsl | 8 +- 78_ChainedScanUnitTest/main.cpp | 226 ++++++++---------- 2 files changed, 101 insertions(+), 133 deletions(-) diff --git a/78_ChainedScanUnitTest/app_resources/testGlobal.comp.hlsl b/78_ChainedScanUnitTest/app_resources/testGlobal.comp.hlsl index e6aa6cbde..4fcc2f788 100644 --- a/78_ChainedScanUnitTest/app_resources/testGlobal.comp.hlsl +++ b/78_ChainedScanUnitTest/app_resources/testGlobal.comp.hlsl @@ -1,12 +1,10 @@ - #pragma shader_stage(compute) - -#define operation_t nbl::hlsl::OPERATION +#pragma shader_stage(compute) #include "nbl/builtin/hlsl/glsl_compat/core.hlsl" #include "nbl/builtin/hlsl/glsl_compat/subgroup_basic.hlsl" #include "nbl/builtin/hlsl/subgroup2/arithmetic_portability.hlsl" -#include "nbl/builtin/hlsl/subgroup2/arithmetic_params.hlsl" +#include "nbl/builtin/hlsl/bda/legacy_bda_accessor.hlsl" #include "nbl/builtin/hlsl/scan/chained_scan.hlsl" using config_t = WORKGROUP_CONFIG_T; @@ -159,7 +157,7 @@ void test() subtest >(); } -[numthreads(WORKGROUP_SIZE,1,1)] +[numthreads(config_t::WorkgroupSize,1,1)] void main() { test(); diff --git a/78_ChainedScanUnitTest/main.cpp b/78_ChainedScanUnitTest/main.cpp index 2fa978953..36b04d0bc 100644 --- a/78_ChainedScanUnitTest/main.cpp +++ b/78_ChainedScanUnitTest/main.cpp @@ -51,13 +51,15 @@ struct emulatedScanExclusive static inline constexpr const char* name = "exclusive_scan"; }; -class Workgroup2ScanTestApp final : public application_templates::BasicMultiQueueApplication, public examples::BuiltinResourcesApplication +class ChainedScanTestApp final : public application_templates::BasicMultiQueueApplication, public examples::BuiltinResourcesApplication { using device_base_t = application_templates::BasicMultiQueueApplication; using asset_base_t = examples::BuiltinResourcesApplication; + constexpr static inline uint32_t ElementCount = 1024 * 1024; + public: - Workgroup2ScanTestApp(const path& _localInputCWD, const path& _localOutputCWD, const path& _sharedInputCWD, const path& _sharedOutputCWD) : + ChainedScanTestApp(const path& _localInputCWD, const path& _localOutputCWD, const path& _sharedInputCWD, const path& _sharedOutputCWD) : system::IApplicationFramework(_localInputCWD, _localOutputCWD, _sharedInputCWD, _sharedOutputCWD) {} bool onAppInitialized(smart_refctd_ptr&& system) override @@ -71,17 +73,16 @@ class Workgroup2ScanTestApp final : public application_templates::BasicMultiQueu computeQueue = getComputeQueue(); // TODO: get the element count from argv - const uint32_t elementCount = 1024 * 1024; // populate our random data buffer on the CPU and create a GPU copy - inputData = new uint32_t[elementCount]; + inputData = new uint32_t[ElementCount]; smart_refctd_ptr gpuinputDataBuffer; { std::mt19937 randGenerator(0xdeadbeefu); - for (uint32_t i = 0u; i < elementCount; i++) + for (uint32_t i = 0u; i < ElementCount; i++) inputData[i] = randGenerator(); // TODO: change to using xoroshiro, then we can skip having the input buffer at all IGPUBuffer::SCreationParams inputDataBufferCreationParams = {}; - inputDataBufferCreationParams.size = sizeof(uint32_t) * elementCount; + inputDataBufferCreationParams.size = sizeof(uint32_t) * ElementCount; inputDataBufferCreationParams.usage = IGPUBuffer::EUF_STORAGE_BUFFER_BIT | IGPUBuffer::EUF_TRANSFER_DST_BIT | IGPUBuffer::EUF_SHADER_DEVICE_ADDRESS_BIT; m_utils->createFilledDeviceLocalBufferOnDedMem( SIntendedSubmitInfo{.queue=getTransferUpQueue()}, @@ -105,7 +106,26 @@ class Workgroup2ScanTestApp final : public application_templates::BasicMultiQueu auto bufferMem = m_device->allocate(mreq, { outputBuffers[i].get(), IDeviceMemoryAllocation::EMAF_DEVICE_ADDRESS_BIT }); assert(bufferMem.isValid()); } + + const auto MinSubgroupSize = m_physicalDevice->getLimits().minSubgroupSize; + // buffer for workgroup reductions + { + const auto maxReductionsCount = ElementCount / MinSubgroupSize; + IGPUBuffer::SCreationParams params = {}; + params.size = sizeof(uint32_t) * maxReductionsCount; // TODO: could be smaller + params.usage = bitflag(IGPUBuffer::EUF_STORAGE_BUFFER_BIT) | IGPUBuffer::EUF_TRANSFER_DST_BIT | IGPUBuffer::EUF_SHADER_DEVICE_ADDRESS_BIT; + + reduceBuffer = m_device->createBuffer(std::move(params)); + auto mreq = reduceBuffer->getMemoryReqs(); + mreq.memoryTypeBits &= m_physicalDevice->getDeviceLocalMemoryTypeBits(); + assert(mreq.memoryTypeBits); + + auto bufferMem = m_device->allocate(mreq, { reduceBuffer.get(), IDeviceMemoryAllocation::EMAF_DEVICE_ADDRESS_BIT }); + assert(bufferMem.isValid()); + } + pc.pInputBuf = gpuinputDataBuffer->getDeviceAddress(); + pc.pReduceBuf = reduceBuffer->getDeviceAddress(); for (uint32_t i = 0; i < OutputBufferCount; i++) pc.pOutputBuf[i] = outputBuffers[i]->getDeviceAddress(); @@ -164,8 +184,8 @@ class Workgroup2ScanTestApp final : public application_templates::BasicMultiQueu return smart_refctd_ptr_static_cast(firstAssetInBundle); }; - auto subgroupTestSource = getShaderSource("app_resources/testSubgroup.comp.hlsl"); - auto workgroupTestSource = getShaderSource("app_resources/testWorkgroup.comp.hlsl"); + auto globalTestSource = getShaderSource("app_resources/testGlobal.comp.hlsl"); + //auto workgroupTestSource = getShaderSource("app_resources/testWorkgroup.comp.hlsl"); // now create or retrieve final resources to run our tests sema = m_device->createSemaphore(timelineValue); resultsBuffer = ICPUBuffer::create({ outputBuffers[0]->getSize() }); @@ -179,7 +199,6 @@ class Workgroup2ScanTestApp final : public application_templates::BasicMultiQueu } const auto MaxWorkgroupSize = m_physicalDevice->getLimits().maxComputeWorkGroupInvocations; - const auto MinSubgroupSize = m_physicalDevice->getLimits().minSubgroupSize; const auto MaxSubgroupSize = m_physicalDevice->getLimits().maxSubgroupSize; for (uint32_t useNative = 0; useNative <= uint32_t(m_physicalDevice->getProperties().limits.shaderSubgroupArithmetic); useNative++) { @@ -200,25 +219,17 @@ class Workgroup2ScanTestApp final : public application_templates::BasicMultiQueu for (uint32_t j = 0; j < ItemsPerInvocations.size(); j++) { const uint32_t itemsPerInvocation = ItemsPerInvocations[j]; - uint32_t itemsPerWG = workgroupSize * itemsPerInvocation; - m_logger->log("Testing Items per Invocation %u", ILogger::ELL_INFO, itemsPerInvocation); bool passed = true; - passed = runTest(subgroupTestSource, elementCount, subgroupSizeLog2, workgroupSize, bool(useNative), itemsPerWG, itemsPerInvocation) && passed; - logTestOutcome(passed, itemsPerWG); - passed = runTest(subgroupTestSource, elementCount, subgroupSizeLog2, workgroupSize, bool(useNative), itemsPerWG, itemsPerInvocation) && passed; - logTestOutcome(passed, itemsPerWG); - passed = runTest(subgroupTestSource, elementCount, subgroupSizeLog2, workgroupSize, bool(useNative), itemsPerWG, itemsPerInvocation) && passed; - logTestOutcome(passed, itemsPerWG); hlsl::workgroup2::SArithmeticConfiguration wgConfig; wgConfig.init(hlsl::findMSB(workgroupSize), subgroupSizeLog2, itemsPerInvocation); - itemsPerWG = wgConfig.VirtualWorkgroupSize * wgConfig.ItemsPerInvocation_0; + uint32_t itemsPerWG = wgConfig.VirtualWorkgroupSize * wgConfig.ItemsPerInvocation_0; m_logger->log("Testing Item Count %u", ILogger::ELL_INFO, itemsPerWG); - passed = runTest(workgroupTestSource, elementCount, subgroupSizeLog2, workgroupSize, bool(useNative), itemsPerWG, itemsPerInvocation) && passed; - logTestOutcome(passed, itemsPerWG); - passed = runTest(workgroupTestSource, elementCount, subgroupSizeLog2, workgroupSize, bool(useNative), itemsPerWG, itemsPerInvocation) && passed; + //passed = runTest(globalTestSource, elementCount, subgroupSizeLog2, workgroupSize, bool(useNative), itemsPerWG, itemsPerInvocation) && passed; + //logTestOutcome(passed, itemsPerWG); + passed = runTest(globalTestSource, ElementCount, subgroupSizeLog2, workgroupSize, bool(useNative), itemsPerWG, itemsPerInvocation) && passed; logTestOutcome(passed, itemsPerWG); - passed = runTest(workgroupTestSource, elementCount, subgroupSizeLog2, workgroupSize, bool(useNative), itemsPerWG, itemsPerInvocation) && passed; + passed = runTest(globalTestSource, ElementCount, subgroupSizeLog2, workgroupSize, bool(useNative), itemsPerWG, itemsPerInvocation) && passed; logTestOutcome(passed, itemsPerWG); } m_api->endCapture(); @@ -285,7 +296,7 @@ class Workgroup2ScanTestApp final : public application_templates::BasicMultiQueu return pipeline; } - template class Arithmetic, bool WorkgroupTest> + template class Arithmetic> bool runTest(const smart_refctd_ptr& source, const uint32_t elementCount, const uint8_t subgroupSizeLog2, const uint32_t workgroupSize, bool useNative, uint32_t itemsPerWG, uint32_t itemsPerInvoc = 1u) { std::string arith_name = Arithmetic>::name; @@ -308,63 +319,56 @@ class Workgroup2ScanTestApp final : public application_templates::BasicMultiQueu auto* includeFinder = compiler->getDefaultIncludeFinder(); options.preprocessorOptions.includeFinder = includeFinder; + + hlsl::workgroup2::SArithmeticConfiguration wgConfig; + wgConfig.init(hlsl::findMSB(workgroupSize), subgroupSizeLog2, itemsPerInvoc); + + const std::string definitions[3] = { + "scan::" + arith_name, + wgConfig.getConfigTemplateStructString(), + std::to_string(arith_name=="reduction") + }; - smart_refctd_ptr overriddenUnspecialized; - if constexpr (WorkgroupTest) - { - hlsl::workgroup2::SArithmeticConfiguration wgConfig; - wgConfig.init(hlsl::findMSB(workgroupSize), subgroupSizeLog2, itemsPerInvoc); - - const std::string definitions[3] = { - "workgroup2::" + arith_name, - wgConfig.getConfigTemplateStructString(), - std::to_string(arith_name=="reduction") - }; - - const IShaderCompiler::SMacroDefinition defines[4] = { - { "OPERATION", definitions[0] }, - { "WORKGROUP_CONFIG_T", definitions[1] }, - { "IS_REDUCTION", definitions[2] }, - { "TEST_NATIVE", "1" } - }; - if (useNative) - options.preprocessorOptions.extraDefines = { defines, defines + 4 }; - else - options.preprocessorOptions.extraDefines = { defines, defines + 3 }; - - overriddenUnspecialized = compiler->compileToSPIRV((const char*)source->getContent()->getPointer(), options); - } + const IShaderCompiler::SMacroDefinition defines[4] = { + { "OPERATION", definitions[0] }, + { "WORKGROUP_CONFIG_T", definitions[1] }, + { "IS_REDUCTION", definitions[2] }, + { "TEST_NATIVE", "1" } + }; + if (useNative) + options.preprocessorOptions.extraDefines = { defines, defines + 4 }; else - { - hlsl::subgroup2::SArithmeticParams sgParams; - sgParams.init(subgroupSizeLog2, itemsPerInvoc); + options.preprocessorOptions.extraDefines = { defines, defines + 3 }; - const std::string definitions[3] = { - "subgroup2::" + arith_name, - std::to_string(workgroupSize), - sgParams.getParamTemplateStructString() - }; - - const IShaderCompiler::SMacroDefinition defines[4] = { - { "OPERATION", definitions[0] }, - { "WORKGROUP_SIZE", definitions[1] }, - { "SUBGROUP_CONFIG_T", definitions[2] }, - { "TEST_NATIVE", "1" } - }; - if (useNative) - options.preprocessorOptions.extraDefines = { defines, defines + 4 }; - else - options.preprocessorOptions.extraDefines = { defines, defines + 3 }; - - overriddenUnspecialized = compiler->compileToSPIRV((const char*)source->getContent()->getPointer(), options); - } + smart_refctd_ptr overriddenUnspecialized = compiler->compileToSPIRV((const char*)source->getContent()->getPointer(), options); auto pipeline = createPipeline(overriddenUnspecialized.get(),subgroupSizeLog2); // TODO: overlap dispatches with memory readbacks (requires multiple copies of `buffers`) - uint32_t workgroupCount = 1;// min(elementCount / itemsPerWG, m_physicalDevice->getLimits().maxComputeWorkGroupCount[0]); + uint32_t workgroupCount = min(elementCount / itemsPerWG, m_physicalDevice->getLimits().maxComputeWorkGroupCount[0]); cmdbuf->begin(IGPUCommandBuffer::USAGE::NONE); + cmdbuf->fillBuffer({ .offset = 0,.size = reduceBuffer->getSize(),.buffer = reduceBuffer}, 0); + { + using buffer_barrier_t = IGPUCommandBuffer::SBufferMemoryBarrier; + const buffer_barrier_t bufBarrier = { + .barrier = { + .dep = { + .srcStageMask = PIPELINE_STAGE_FLAGS::CLEAR_BIT | PIPELINE_STAGE_FLAGS::COPY_BIT, + .srcAccessMask = ACCESS_FLAGS::TRANSFER_WRITE_BIT, + .dstStageMask = PIPELINE_STAGE_FLAGS::COMPUTE_SHADER_BIT, + .dstAccessMask = ACCESS_FLAGS::SHADER_READ_BITS | ACCESS_FLAGS::SHADER_WRITE_BITS + } // no ownership transfers, etc. + }, + .range = {.offset = 0,.size = reduceBuffer->getSize(),.buffer = reduceBuffer} + }; + cmdbuf->pipelineBarrier(E_DEPENDENCY_FLAGS::EDF_NONE, { + .memBarriers = {}, + .bufBarriers = {&bufBarrier,1}, + .imgBarriers = {} + }); + } + cmdbuf->bindComputePipeline(pipeline.get()); cmdbuf->pushConstants(pipelineLayout.get(), IShader::E_SHADER_STAGE::ESS_COMPUTE, 0, sizeof(PushConstantData), &pc); cmdbuf->dispatch(workgroupCount, 1, 1); @@ -399,19 +403,19 @@ class Workgroup2ScanTestApp final : public application_templates::BasicMultiQueu const uint32_t subgroupSize = 1u << subgroupSizeLog2; // check results - bool passed = validateResults, WorkgroupTest>(itemsPerWG, workgroupCount, subgroupSize, itemsPerInvoc); - passed = validateResults, WorkgroupTest>(itemsPerWG, workgroupCount, subgroupSize, itemsPerInvoc) && passed; - passed = validateResults, WorkgroupTest>(itemsPerWG, workgroupCount, subgroupSize, itemsPerInvoc) && passed; - passed = validateResults, WorkgroupTest>(itemsPerWG, workgroupCount, subgroupSize, itemsPerInvoc) && passed; - passed = validateResults, WorkgroupTest>(itemsPerWG, workgroupCount, subgroupSize, itemsPerInvoc) && passed; - passed = validateResults, WorkgroupTest>(itemsPerWG, workgroupCount, subgroupSize, itemsPerInvoc) && passed; - passed = validateResults, WorkgroupTest>(itemsPerWG, workgroupCount, subgroupSize, itemsPerInvoc) && passed; + bool passed = validateResults>(itemsPerWG, workgroupCount, subgroupSize, itemsPerInvoc); + passed = validateResults>(itemsPerWG, workgroupCount, subgroupSize, itemsPerInvoc) && passed; + passed = validateResults>(itemsPerWG, workgroupCount, subgroupSize, itemsPerInvoc) && passed; + passed = validateResults>(itemsPerWG, workgroupCount, subgroupSize, itemsPerInvoc) && passed; + passed = validateResults>(itemsPerWG, workgroupCount, subgroupSize, itemsPerInvoc) && passed; + passed = validateResults>(itemsPerWG, workgroupCount, subgroupSize, itemsPerInvoc) && passed; + passed = validateResults>(itemsPerWG, workgroupCount, subgroupSize, itemsPerInvoc) && passed; return passed; } //returns true if result matches - template class Arithmetic, class Binop, bool WorkgroupTest> + template class Arithmetic, class Binop> bool validateResults(const uint32_t itemsPerWG, const uint32_t workgroupCount, const uint32_t subgroupSize, const uint32_t itemsPerInvoc) { bool success = true; @@ -425,58 +429,23 @@ class Workgroup2ScanTestApp final : public application_templates::BasicMultiQueu // TODO: parallel for (the temporary values need to be threadlocal or what?) // now check if the data obtained has valid values - type_t* tmp = new type_t[itemsPerWG]; - for (uint32_t workgroupID = 0u; success && workgroupID < workgroupCount; workgroupID++) + type_t* tmp = new type_t[ElementCount]; { - if constexpr (WorkgroupTest) - { - const auto workgroupOffset = workgroupID * itemsPerWG; - Arithmetic::impl(tmp, inputData + workgroupOffset, itemsPerWG); + Arithmetic::impl(tmp, inputData, ElementCount); - for (uint32_t localInvocationIndex = 0u; localInvocationIndex < itemsPerWG; localInvocationIndex++) - { - const auto globalInvocationIndex = workgroupOffset + localInvocationIndex; - const auto cpuVal = tmp[localInvocationIndex]; - const auto gpuVal = testData[globalInvocationIndex]; - if (cpuVal != gpuVal) - { - m_logger->log( - "Failed test #%d (%s) (%s) Expected %u got %u for workgroup %d and localinvoc %d", - ILogger::ELL_ERROR, itemsPerWG, WorkgroupTest ? "workgroup" : "subgroup", Binop::name, - cpuVal, gpuVal, workgroupID, localInvocationIndex - ); - success = false; - break; - } - } - } - else + for (uint32_t i = 0u; i < ElementCount; i++) { - const auto workgroupOffset = workgroupID * itemsPerWG; - const auto workgroupSize = itemsPerWG / itemsPerInvoc; - for (uint32_t pseudoSubgroupID = 0u; pseudoSubgroupID < workgroupSize; pseudoSubgroupID += subgroupSize) - Arithmetic::impl(tmp + pseudoSubgroupID * itemsPerInvoc, inputData + workgroupOffset + pseudoSubgroupID * itemsPerInvoc, subgroupSize * itemsPerInvoc); - - for (uint32_t localInvocationIndex = 0u; localInvocationIndex < workgroupSize; localInvocationIndex++) + const auto cpuVal = tmp[i]; + const auto gpuVal = testData[i]; + if (cpuVal != gpuVal) { - const auto localOffset = localInvocationIndex * itemsPerInvoc; - const auto globalInvocationIndex = workgroupOffset + localOffset; - - for (uint32_t itemInvocationIndex = 0u; itemInvocationIndex < itemsPerInvoc; itemInvocationIndex++) - { - const auto cpuVal = tmp[localOffset + itemInvocationIndex]; - const auto gpuVal = testData[globalInvocationIndex + itemInvocationIndex]; - if (cpuVal != gpuVal) - { - m_logger->log( - "Failed test #%d (%s) (%s) Expected %u got %u for workgroup %d and localinvoc %d and iteminvoc %d", - ILogger::ELL_ERROR, itemsPerWG, WorkgroupTest ? "workgroup" : "subgroup", Binop::name, - cpuVal, gpuVal, workgroupID, localInvocationIndex, itemInvocationIndex - ); - success = false; - break; - } - } + m_logger->log( + "Failed test #%d (%s) (%s) Expected %u got %u for element number %d", + ILogger::ELL_ERROR, itemsPerWG, "global", Binop::name, + cpuVal, gpuVal, i + ); + success = false; + break; } } } @@ -493,6 +462,7 @@ class Workgroup2ScanTestApp final : public application_templates::BasicMultiQueu uint32_t* inputData = nullptr; constexpr static inline uint32_t OutputBufferCount = 8u; smart_refctd_ptr outputBuffers[OutputBufferCount]; + smart_refctd_ptr reduceBuffer; smart_refctd_ptr pipelineLayout; PushConstantData pc; @@ -506,4 +476,4 @@ class Workgroup2ScanTestApp final : public application_templates::BasicMultiQueu constexpr static inline std::array ItemsPerInvocations = { 1, 2, 3, 4 }; }; -NBL_MAIN_FUNC(Workgroup2ScanTestApp) \ No newline at end of file +NBL_MAIN_FUNC(ChainedScanTestApp) \ No newline at end of file From 0e0ad6c2d7f3943ee8b7922172cce55d85bc6c11 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Tue, 21 Jul 2026 12:05:56 +0700 Subject: [PATCH 3/8] fix test bugs, only do one op per test shader run --- .../app_resources/testGlobal.comp.hlsl | 12 ++++----- 78_ChainedScanUnitTest/main.cpp | 27 +++++++++++-------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/78_ChainedScanUnitTest/app_resources/testGlobal.comp.hlsl b/78_ChainedScanUnitTest/app_resources/testGlobal.comp.hlsl index 4fcc2f788..c7429bd7f 100644 --- a/78_ChainedScanUnitTest/app_resources/testGlobal.comp.hlsl +++ b/78_ChainedScanUnitTest/app_resources/testGlobal.comp.hlsl @@ -148,13 +148,13 @@ static void subtest() void test() { - subtest >(); - subtest >(); - subtest >(); + // subtest >(); + // subtest >(); + // subtest >(); subtest >(); - subtest >(); - subtest >(); - subtest >(); + // subtest >(); + // subtest >(); + // subtest >(); } [numthreads(config_t::WorkgroupSize,1,1)] diff --git a/78_ChainedScanUnitTest/main.cpp b/78_ChainedScanUnitTest/main.cpp index 36b04d0bc..621050727 100644 --- a/78_ChainedScanUnitTest/main.cpp +++ b/78_ChainedScanUnitTest/main.cpp @@ -79,7 +79,7 @@ class ChainedScanTestApp final : public application_templates::BasicMultiQueueAp { std::mt19937 randGenerator(0xdeadbeefu); for (uint32_t i = 0u; i < ElementCount; i++) - inputData[i] = randGenerator(); // TODO: change to using xoroshiro, then we can skip having the input buffer at all + inputData[i] = 1u;// randGenerator(); // TODO: change to using xoroshiro, then we can skip having the input buffer at all IGPUBuffer::SCreationParams inputDataBufferCreationParams = {}; inputDataBufferCreationParams.size = sizeof(uint32_t) * ElementCount; @@ -89,6 +89,8 @@ class ChainedScanTestApp final : public application_templates::BasicMultiQueueAp std::move(inputDataBufferCreationParams), inputData ).move_into(gpuinputDataBuffer); + + gpuinputDataBuffer->setObjectDebugName("Input data buffer"); } // create 8 buffers for 8 operations @@ -105,6 +107,8 @@ class ChainedScanTestApp final : public application_templates::BasicMultiQueueAp auto bufferMem = m_device->allocate(mreq, { outputBuffers[i].get(), IDeviceMemoryAllocation::EMAF_DEVICE_ADDRESS_BIT }); assert(bufferMem.isValid()); + + outputBuffers[i]->setObjectDebugName(("Output data buffer #" + std::to_string(i)).c_str()); } const auto MinSubgroupSize = m_physicalDevice->getLimits().minSubgroupSize; @@ -122,6 +126,8 @@ class ChainedScanTestApp final : public application_templates::BasicMultiQueueAp auto bufferMem = m_device->allocate(mreq, { reduceBuffer.get(), IDeviceMemoryAllocation::EMAF_DEVICE_ADDRESS_BIT }); assert(bufferMem.isValid()); + + reduceBuffer->setObjectDebugName("Workgroup reduction buffer"); } pc.pInputBuf = gpuinputDataBuffer->getDeviceAddress(); @@ -207,10 +213,10 @@ class ChainedScanTestApp final : public application_templates::BasicMultiQueueAp else m_logger->log("Testing with emulated subgroup arithmetic", ILogger::ELL_INFO); - for (auto subgroupSize = MinSubgroupSize; subgroupSize <= MaxSubgroupSize; subgroupSize *= 2u) + for (auto subgroupSize = 32; subgroupSize <= MaxSubgroupSize; subgroupSize *= 2u) { const uint8_t subgroupSizeLog2 = hlsl::findMSB(subgroupSize); - for (uint32_t workgroupSize = subgroupSize; workgroupSize <= MaxWorkgroupSize; workgroupSize *= 2u) + for (uint32_t workgroupSize = 1024; workgroupSize <= MaxWorkgroupSize; workgroupSize *= 2u) { // make sure renderdoc captures everything for debugging m_api->startCapture(); @@ -225,8 +231,6 @@ class ChainedScanTestApp final : public application_templates::BasicMultiQueueAp wgConfig.init(hlsl::findMSB(workgroupSize), subgroupSizeLog2, itemsPerInvocation); uint32_t itemsPerWG = wgConfig.VirtualWorkgroupSize * wgConfig.ItemsPerInvocation_0; m_logger->log("Testing Item Count %u", ILogger::ELL_INFO, itemsPerWG); - //passed = runTest(globalTestSource, elementCount, subgroupSizeLog2, workgroupSize, bool(useNative), itemsPerWG, itemsPerInvocation) && passed; - //logTestOutcome(passed, itemsPerWG); passed = runTest(globalTestSource, ElementCount, subgroupSizeLog2, workgroupSize, bool(useNative), itemsPerWG, itemsPerInvocation) && passed; logTestOutcome(passed, itemsPerWG); passed = runTest(globalTestSource, ElementCount, subgroupSizeLog2, workgroupSize, bool(useNative), itemsPerWG, itemsPerInvocation) && passed; @@ -403,13 +407,14 @@ class ChainedScanTestApp final : public application_templates::BasicMultiQueueAp const uint32_t subgroupSize = 1u << subgroupSizeLog2; // check results - bool passed = validateResults>(itemsPerWG, workgroupCount, subgroupSize, itemsPerInvoc); - passed = validateResults>(itemsPerWG, workgroupCount, subgroupSize, itemsPerInvoc) && passed; - passed = validateResults>(itemsPerWG, workgroupCount, subgroupSize, itemsPerInvoc) && passed; + bool passed = true; + //passed = validateResults>(itemsPerWG, workgroupCount, subgroupSize, itemsPerInvoc); + //passed = validateResults>(itemsPerWG, workgroupCount, subgroupSize, itemsPerInvoc) && passed; + //passed = validateResults>(itemsPerWG, workgroupCount, subgroupSize, itemsPerInvoc) && passed; passed = validateResults>(itemsPerWG, workgroupCount, subgroupSize, itemsPerInvoc) && passed; - passed = validateResults>(itemsPerWG, workgroupCount, subgroupSize, itemsPerInvoc) && passed; - passed = validateResults>(itemsPerWG, workgroupCount, subgroupSize, itemsPerInvoc) && passed; - passed = validateResults>(itemsPerWG, workgroupCount, subgroupSize, itemsPerInvoc) && passed; + //passed = validateResults>(itemsPerWG, workgroupCount, subgroupSize, itemsPerInvoc) && passed; + //passed = validateResults>(itemsPerWG, workgroupCount, subgroupSize, itemsPerInvoc) && passed; + //passed = validateResults>(itemsPerWG, workgroupCount, subgroupSize, itemsPerInvoc) && passed; return passed; } From 60316838198631779e2130e90e51a84672510b6c Mon Sep 17 00:00:00 2001 From: keptsecret Date: Wed, 22 Jul 2026 14:42:07 +0700 Subject: [PATCH 4/8] atomicMax for workgroup reduction accessor --- 78_ChainedScanUnitTest/app_resources/testGlobal.comp.hlsl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/78_ChainedScanUnitTest/app_resources/testGlobal.comp.hlsl b/78_ChainedScanUnitTest/app_resources/testGlobal.comp.hlsl index c7429bd7f..2f99b577a 100644 --- a/78_ChainedScanUnitTest/app_resources/testGlobal.comp.hlsl +++ b/78_ChainedScanUnitTest/app_resources/testGlobal.comp.hlsl @@ -104,6 +104,11 @@ struct ReduceAccessor return target.template deref().store(value); } + T atomicMax(const uint64_t index, const T value) + { + bda::__ptr target = ptr + index; + return glsl::atomicMax(target.template deref().ptr.value, value); + } T atomicExchange(const uint64_t index, const T value) { bda::__ptr target = ptr + index; From 15cf7db09af9071cde411d34fd5a44856b1c756f Mon Sep 17 00:00:00 2001 From: keptsecret Date: Thu, 23 Jul 2026 17:04:48 +0700 Subject: [PATCH 5/8] impl workgroup counter, reduce test input range --- .../app_resources/common.hlsl | 1 + .../app_resources/testGlobal.comp.hlsl | 35 ++++++++++- 78_ChainedScanUnitTest/main.cpp | 61 ++++++++++++++----- 3 files changed, 79 insertions(+), 18 deletions(-) diff --git a/78_ChainedScanUnitTest/app_resources/common.hlsl b/78_ChainedScanUnitTest/app_resources/common.hlsl index 5465dc6fa..b10f7da53 100644 --- a/78_ChainedScanUnitTest/app_resources/common.hlsl +++ b/78_ChainedScanUnitTest/app_resources/common.hlsl @@ -6,6 +6,7 @@ struct PushConstantData uint64_t pInputBuf; uint64_t pOutputBuf[8]; uint64_t pReduceBuf; + uint64_t pWgCounterBuf; }; namespace arithmetic diff --git a/78_ChainedScanUnitTest/app_resources/testGlobal.comp.hlsl b/78_ChainedScanUnitTest/app_resources/testGlobal.comp.hlsl index 2f99b577a..3cc548979 100644 --- a/78_ChainedScanUnitTest/app_resources/testGlobal.comp.hlsl +++ b/78_ChainedScanUnitTest/app_resources/testGlobal.comp.hlsl @@ -50,9 +50,17 @@ struct DataProxy DataProxy retval; const uint32_t workgroupOffset = glsl::gl_WorkGroupID().x * VirtualWorkgroupSize * sizeof(dtype_t); retval.accessor = DoubleLegacyBdaAccessor::create(inputBuf + workgroupOffset, outputBuf + workgroupOffset); + retval.inputAddress = inputBuf; + retval.outputAddress = outputBuf; return retval; } + void initAtWorkgroupID(const uint32_t workgroupID) + { + const uint32_t workgroupOffset = workgroupID * VirtualWorkgroupSize * sizeof(dtype_t); + accessor = DoubleLegacyBdaAccessor::create(inputAddress + workgroupOffset, outputAddress + workgroupOffset); + } + template void get(const IndexType ix, NBL_REF_ARG(AccessType) value) { @@ -66,11 +74,11 @@ struct DataProxy uint64_t getInputBufAddr() { - return accessor.inputAddress; + return inputAddress; } uint64_t getOutputBufAddr() { - return accessor.outputAddress; + return outputAddress; } void workgroupExecutionAndMemoryBarrier() @@ -80,6 +88,7 @@ struct DataProxy } DoubleLegacyBdaAccessor accessor; + uint64_t inputAddress, outputAddress; }; template @@ -118,6 +127,24 @@ struct ReduceAccessor bda::__ptr ptr; }; +struct WorkgroupCounter +{ + static WorkgroupCounter create(const uint64_t addr) + { + WorkgroupCounter retval; + retval.ptr = bda::__ptr::create(addr); + return retval; + } + + uint32_t atomicAdd(const uint64_t index, const uint32_t value) // TODO: maybe it should be just increment + { + bda::__ptr target = ptr + index; + return glsl::atomicAdd(target.template deref().ptr.value, value); + } + + bda::__ptr ptr; +}; + static ScratchProxy arithmeticAccessor; template @@ -135,7 +162,9 @@ struct operation_t bda::__ptr ptr = bda::__ptr::create(pc.pReduceBuf); reduce_proxy_t reduceAccessor = reduce_proxy_t::create(ptr); - OPERATION::template __call(dataAccessor,arithmeticAccessor,reduceAccessor); + WorkgroupCounter wgCounter = WorkgroupCounter::create(pc.pWgCounterBuf); + + OPERATION::template __call(dataAccessor,arithmeticAccessor,reduceAccessor,wgCounter); // we barrier before because we alias the accessors for Binop arithmeticAccessor.workgroupExecutionAndMemoryBarrier(); } diff --git a/78_ChainedScanUnitTest/main.cpp b/78_ChainedScanUnitTest/main.cpp index 621050727..fbe43a025 100644 --- a/78_ChainedScanUnitTest/main.cpp +++ b/78_ChainedScanUnitTest/main.cpp @@ -78,8 +78,9 @@ class ChainedScanTestApp final : public application_templates::BasicMultiQueueAp smart_refctd_ptr gpuinputDataBuffer; { std::mt19937 randGenerator(0xdeadbeefu); + std::uniform_int_distribution<> distrib(0, 100); for (uint32_t i = 0u; i < ElementCount; i++) - inputData[i] = 1u;// randGenerator(); // TODO: change to using xoroshiro, then we can skip having the input buffer at all + inputData[i] = i % 1024;// distrib(randGenerator); // TODO: change to using xoroshiro, then we can skip having the input buffer at all IGPUBuffer::SCreationParams inputDataBufferCreationParams = {}; inputDataBufferCreationParams.size = sizeof(uint32_t) * ElementCount; @@ -129,9 +130,25 @@ class ChainedScanTestApp final : public application_templates::BasicMultiQueueAp reduceBuffer->setObjectDebugName("Workgroup reduction buffer"); } + { + IGPUBuffer::SCreationParams params = {}; + params.size = sizeof(uint32_t); + params.usage = bitflag(IGPUBuffer::EUF_STORAGE_BUFFER_BIT) | IGPUBuffer::EUF_TRANSFER_DST_BIT | IGPUBuffer::EUF_SHADER_DEVICE_ADDRESS_BIT; + + wgCounterBuffer = m_device->createBuffer(std::move(params)); + auto mreq = wgCounterBuffer->getMemoryReqs(); + mreq.memoryTypeBits &= m_physicalDevice->getDeviceLocalMemoryTypeBits(); + assert(mreq.memoryTypeBits); + + auto bufferMem = m_device->allocate(mreq, { wgCounterBuffer.get(), IDeviceMemoryAllocation::EMAF_DEVICE_ADDRESS_BIT }); + assert(bufferMem.isValid()); + + wgCounterBuffer->setObjectDebugName("Workgroup counter buffer"); + } pc.pInputBuf = gpuinputDataBuffer->getDeviceAddress(); pc.pReduceBuf = reduceBuffer->getDeviceAddress(); + pc.pWgCounterBuf = wgCounterBuffer->getDeviceAddress(); for (uint32_t i = 0; i < OutputBufferCount; i++) pc.pOutputBuf[i] = outputBuffers[i]->getDeviceAddress(); @@ -191,7 +208,6 @@ class ChainedScanTestApp final : public application_templates::BasicMultiQueueAp }; auto globalTestSource = getShaderSource("app_resources/testGlobal.comp.hlsl"); - //auto workgroupTestSource = getShaderSource("app_resources/testWorkgroup.comp.hlsl"); // now create or retrieve final resources to run our tests sema = m_device->createSemaphore(timelineValue); resultsBuffer = ICPUBuffer::create({ outputBuffers[0]->getSize() }); @@ -353,23 +369,37 @@ class ChainedScanTestApp final : public application_templates::BasicMultiQueueAp cmdbuf->begin(IGPUCommandBuffer::USAGE::NONE); cmdbuf->fillBuffer({ .offset = 0,.size = reduceBuffer->getSize(),.buffer = reduceBuffer}, 0); + cmdbuf->fillBuffer({ .offset = 0,.size = wgCounterBuffer->getSize(),.buffer = wgCounterBuffer }, 0); { using buffer_barrier_t = IGPUCommandBuffer::SBufferMemoryBarrier; - const buffer_barrier_t bufBarrier = { - .barrier = { - .dep = { - .srcStageMask = PIPELINE_STAGE_FLAGS::CLEAR_BIT | PIPELINE_STAGE_FLAGS::COPY_BIT, - .srcAccessMask = ACCESS_FLAGS::TRANSFER_WRITE_BIT, - .dstStageMask = PIPELINE_STAGE_FLAGS::COMPUTE_SHADER_BIT, - .dstAccessMask = ACCESS_FLAGS::SHADER_READ_BITS | ACCESS_FLAGS::SHADER_WRITE_BITS - } // no ownership transfers, etc. - }, - .range = {.offset = 0,.size = reduceBuffer->getSize(),.buffer = reduceBuffer} + buffer_barrier_t bufBarrier[2]; + bufBarrier[0] = { + .barrier = { + .dep = { + .srcStageMask = PIPELINE_STAGE_FLAGS::CLEAR_BIT | PIPELINE_STAGE_FLAGS::COPY_BIT, + .srcAccessMask = ACCESS_FLAGS::TRANSFER_WRITE_BIT, + .dstStageMask = PIPELINE_STAGE_FLAGS::COMPUTE_SHADER_BIT, + .dstAccessMask = ACCESS_FLAGS::SHADER_READ_BITS | ACCESS_FLAGS::SHADER_WRITE_BITS + } // no ownership transfers, etc. + }, + .range = {.offset = 0,.size = reduceBuffer->getSize(),.buffer = reduceBuffer} + }; + bufBarrier[1] = { + .barrier = { + .dep = { + .srcStageMask = PIPELINE_STAGE_FLAGS::CLEAR_BIT | PIPELINE_STAGE_FLAGS::COPY_BIT, + .srcAccessMask = ACCESS_FLAGS::TRANSFER_WRITE_BIT, + .dstStageMask = PIPELINE_STAGE_FLAGS::COMPUTE_SHADER_BIT, + .dstAccessMask = ACCESS_FLAGS::SHADER_READ_BITS | ACCESS_FLAGS::SHADER_WRITE_BITS + } // no ownership transfers, etc. + }, + .range = {.offset = 0,.size = wgCounterBuffer->getSize(),.buffer = wgCounterBuffer} }; + cmdbuf->pipelineBarrier(E_DEPENDENCY_FLAGS::EDF_NONE, { - .memBarriers = {}, - .bufBarriers = {&bufBarrier,1}, - .imgBarriers = {} + .memBarriers = {}, + .bufBarriers = bufBarrier, + .imgBarriers = {} }); } @@ -468,6 +498,7 @@ class ChainedScanTestApp final : public application_templates::BasicMultiQueueAp constexpr static inline uint32_t OutputBufferCount = 8u; smart_refctd_ptr outputBuffers[OutputBufferCount]; smart_refctd_ptr reduceBuffer; + smart_refctd_ptr wgCounterBuffer; smart_refctd_ptr pipelineLayout; PushConstantData pc; From 69036ae8c324947fb47c0bc76ed9bf111ae89a55 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Mon, 27 Jul 2026 16:25:52 +0700 Subject: [PATCH 6/8] fix workgroup dispatch count, input random number range only to 100 --- 78_ChainedScanUnitTest/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/78_ChainedScanUnitTest/main.cpp b/78_ChainedScanUnitTest/main.cpp index fbe43a025..d130f0a7a 100644 --- a/78_ChainedScanUnitTest/main.cpp +++ b/78_ChainedScanUnitTest/main.cpp @@ -80,7 +80,7 @@ class ChainedScanTestApp final : public application_templates::BasicMultiQueueAp std::mt19937 randGenerator(0xdeadbeefu); std::uniform_int_distribution<> distrib(0, 100); for (uint32_t i = 0u; i < ElementCount; i++) - inputData[i] = i % 1024;// distrib(randGenerator); // TODO: change to using xoroshiro, then we can skip having the input buffer at all + inputData[i] = distrib(randGenerator); // TODO: change to using xoroshiro, then we can skip having the input buffer at all IGPUBuffer::SCreationParams inputDataBufferCreationParams = {}; inputDataBufferCreationParams.size = sizeof(uint32_t) * ElementCount; @@ -365,7 +365,7 @@ class ChainedScanTestApp final : public application_templates::BasicMultiQueueAp auto pipeline = createPipeline(overriddenUnspecialized.get(),subgroupSizeLog2); // TODO: overlap dispatches with memory readbacks (requires multiple copies of `buffers`) - uint32_t workgroupCount = min(elementCount / itemsPerWG, m_physicalDevice->getLimits().maxComputeWorkGroupCount[0]); + uint32_t workgroupCount = min((elementCount + itemsPerWG - 1) / itemsPerWG, m_physicalDevice->getLimits().maxComputeWorkGroupCount[0]); cmdbuf->begin(IGPUCommandBuffer::USAGE::NONE); cmdbuf->fillBuffer({ .offset = 0,.size = reduceBuffer->getSize(),.buffer = reduceBuffer}, 0); From 28366464346ce79490985cd3cb295674d84835aa Mon Sep 17 00:00:00 2001 From: keptsecret Date: Thu, 30 Jul 2026 16:42:20 +0700 Subject: [PATCH 7/8] restrict random numbers to range 0-100 so it doesn't overflow to flags in scan, memory barrier (might be unnecessary --- 78_ChainedScanUnitTest/app_resources/testGlobal.comp.hlsl | 5 +++++ 78_ChainedScanUnitTest/main.cpp | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/78_ChainedScanUnitTest/app_resources/testGlobal.comp.hlsl b/78_ChainedScanUnitTest/app_resources/testGlobal.comp.hlsl index 3cc548979..ff96e722e 100644 --- a/78_ChainedScanUnitTest/app_resources/testGlobal.comp.hlsl +++ b/78_ChainedScanUnitTest/app_resources/testGlobal.comp.hlsl @@ -124,6 +124,11 @@ struct ReduceAccessor return glsl::atomicExchange(target.template deref().ptr.value, value); } + void memoryBarrier() + { + spirv::memoryBarrier(spv::ScopeDevice, spv::MemorySemanticsAcquireReleaseMask | spv::MemorySemanticsUniformMemoryMask); + } + bda::__ptr ptr; }; diff --git a/78_ChainedScanUnitTest/main.cpp b/78_ChainedScanUnitTest/main.cpp index d130f0a7a..c3e250b49 100644 --- a/78_ChainedScanUnitTest/main.cpp +++ b/78_ChainedScanUnitTest/main.cpp @@ -229,10 +229,10 @@ class ChainedScanTestApp final : public application_templates::BasicMultiQueueAp else m_logger->log("Testing with emulated subgroup arithmetic", ILogger::ELL_INFO); - for (auto subgroupSize = 32; subgroupSize <= MaxSubgroupSize; subgroupSize *= 2u) + for (auto subgroupSize = MinSubgroupSize; subgroupSize <= MaxSubgroupSize; subgroupSize *= 2u) { const uint8_t subgroupSizeLog2 = hlsl::findMSB(subgroupSize); - for (uint32_t workgroupSize = 1024; workgroupSize <= MaxWorkgroupSize; workgroupSize *= 2u) + for (uint32_t workgroupSize = subgroupSize; workgroupSize <= MaxWorkgroupSize; workgroupSize *= 2u) { // make sure renderdoc captures everything for debugging m_api->startCapture(); From 5ac0f19fd46cef5680b92b5fa4fd97f3f2b29fe4 Mon Sep 17 00:00:00 2001 From: keptsecret Date: Fri, 31 Jul 2026 12:15:48 +0700 Subject: [PATCH 8/8] refactor to fit concepts --- 78_ChainedScanUnitTest/app_resources/testGlobal.comp.hlsl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/78_ChainedScanUnitTest/app_resources/testGlobal.comp.hlsl b/78_ChainedScanUnitTest/app_resources/testGlobal.comp.hlsl index ff96e722e..1afa33d95 100644 --- a/78_ChainedScanUnitTest/app_resources/testGlobal.comp.hlsl +++ b/78_ChainedScanUnitTest/app_resources/testGlobal.comp.hlsl @@ -102,12 +102,14 @@ struct ReduceAccessor return retval; } - void get(const uint64_t index, NBL_REF_ARG(T) value) + template + void get(const IndexType index, NBL_REF_ARG(AccessType) value) { bda::__ptr target = ptr + index; value = target.template deref().load(); } - void set(const uint64_t index, const T value) + template + void set(const IndexType index, const AccessType value) { bda::__ptr target = ptr + index; return target.template deref().store(value);