Skip to content
Open
14 changes: 14 additions & 0 deletions 78_ChainedScanUnitTest/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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
)
98 changes: 98 additions & 0 deletions 78_ChainedScanUnitTest/app_resources/common.hlsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#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;
uint64_t pWgCounterBuf;
};

namespace arithmetic
{
// Thanks to our unified HLSL/C++ STD lib we're able to remove a whole load of code
template<typename T>
struct bit_and : nbl::hlsl::bit_and<T>
{
using base_t = nbl::hlsl::bit_and<T>;

NBL_CONSTEXPR_STATIC_INLINE uint16_t BindingIndex = 0;
#ifndef __HLSL_VERSION
static inline constexpr const char* name = "bit_and";
#endif
};
template<typename T>
struct bit_or : nbl::hlsl::bit_or<T>
{
using base_t = nbl::hlsl::bit_or<T>;

NBL_CONSTEXPR_STATIC_INLINE uint16_t BindingIndex = 1;
#ifndef __HLSL_VERSION
static inline constexpr const char* name = "bit_xor";
#endif
};
template<typename T>
struct bit_xor : nbl::hlsl::bit_xor<T>
{
using base_t = nbl::hlsl::bit_xor<T>;

NBL_CONSTEXPR_STATIC_INLINE uint16_t BindingIndex = 2;
#ifndef __HLSL_VERSION
static inline constexpr const char* name = "bit_or";
#endif
};
template<typename T>
struct plus : nbl::hlsl::plus<T>
{
using base_t = nbl::hlsl::plus<T>;

NBL_CONSTEXPR_STATIC_INLINE uint16_t BindingIndex = 3;
#ifndef __HLSL_VERSION
static inline constexpr const char* name = "plus";
#endif
};
template<typename T>
struct multiplies : nbl::hlsl::multiplies<T>
{
using base_t = nbl::hlsl::multiplies<T>;

NBL_CONSTEXPR_STATIC_INLINE uint16_t BindingIndex = 4;
#ifndef __HLSL_VERSION
static inline constexpr const char* name = "multiplies";
#endif
};
template<typename T>
struct minimum : nbl::hlsl::minimum<T>
{
using base_t = nbl::hlsl::minimum<T>;

NBL_CONSTEXPR_STATIC_INLINE uint16_t BindingIndex = 5;
#ifndef __HLSL_VERSION
static inline constexpr const char* name = "minimum";
#endif
};
template<typename T>
struct maximum : nbl::hlsl::maximum<T>
{
using base_t = nbl::hlsl::maximum<T>;

NBL_CONSTEXPR_STATIC_INLINE uint16_t BindingIndex = 6;
#ifndef __HLSL_VERSION
static inline constexpr const char* name = "maximum";
#endif
};

template<typename T>
struct ballot : nbl::hlsl::plus<T>
{
using base_t = nbl::hlsl::plus<T>;

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"
19 changes: 19 additions & 0 deletions 78_ChainedScanUnitTest/app_resources/shaderCommon.hlsl
Original file line number Diff line number Diff line change
@@ -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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

small nitpicking: If you do { "TEST_NATIVE", "1" } to enable this, it should be #if not #ifdef

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indeeed

NBL_CONSTEXPR_STATIC_INLINE bool shaderSubgroupArithmetic = true;
#else
NBL_CONSTEXPR_STATIC_INLINE bool shaderSubgroupArithmetic = false;
#endif
};

#ifndef OPERATION
#error "Define OPERATION!"
#endif
205 changes: 205 additions & 0 deletions 78_ChainedScanUnitTest/app_resources/testGlobal.comp.hlsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
#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/bda/legacy_bda_accessor.hlsl"
#include "nbl/builtin/hlsl/scan/chained_scan.hlsl"

using config_t = WORKGROUP_CONFIG_T;

#include "app_resources/shaderCommon.hlsl"

typedef vector<uint32_t, config_t::ItemsPerInvocation_0> type_t;

groupshared uint32_t scratch[mpl::max_v<int16_t,config_t::SharedScratchElementCount,1>];

struct ScratchProxy
{
template<typename AccessType, typename IndexType>
void get(const uint32_t ix, NBL_REF_ARG(AccessType) value)
{
value = scratch[ix];
}
template<typename AccessType, typename IndexType>
void set(const uint32_t ix, const AccessType value)
{
scratch[ix] = value;
Comment on lines +23 to +28

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

btw if you want these to be visible to other invocations under Vulkan memory model, you might want to use SPIR-V OpLoad MakeVisible and OpStore MakeAvailable with NonPrivatePointer semantics I think

}

uint32_t atomicOr(const uint32_t ix, const uint32_t value)
{
return glsl::atomicOr(scratch[ix],value);
}

void workgroupExecutionAndMemoryBarrier()
{
glsl::barrier();
}
};

template<uint16_t VirtualWorkgroupSize, uint16_t ItemsPerInvocation>
struct DataProxy
{
using dtype_t = vector<uint32_t, ItemsPerInvocation>;
// function template AccessType should be the same as dtype_t

static DataProxy<VirtualWorkgroupSize, ItemsPerInvocation> create(const uint64_t inputBuf, const uint64_t outputBuf)
{
DataProxy<VirtualWorkgroupSize, ItemsPerInvocation> retval;
const uint32_t workgroupOffset = glsl::gl_WorkGroupID().x * VirtualWorkgroupSize * sizeof(dtype_t);
retval.accessor = DoubleLegacyBdaAccessor<dtype_t>::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<dtype_t>::create(inputAddress + workgroupOffset, outputAddress + workgroupOffset);
}

template<typename AccessType, typename IndexType>
void get(const IndexType ix, NBL_REF_ARG(AccessType) value)
{
accessor.get(ix, value);
}
template<typename AccessType, typename IndexType>
void set(const IndexType ix, const AccessType value)
{
accessor.set(ix, value);
}

uint64_t getInputBufAddr()
{
return inputAddress;
}
uint64_t getOutputBufAddr()
{
return outputAddress;
}

void workgroupExecutionAndMemoryBarrier()
{
glsl::barrier();
//glsl::memoryBarrierShared(); implied by the above
}

DoubleLegacyBdaAccessor<dtype_t> accessor;
uint64_t inputAddress, outputAddress;
};

template<typename T>
struct ReduceAccessor
{
using type_t = T;
static ReduceAccessor<T> create(const bda::__ptr<T> ptr)
{
ReduceAccessor<T> retval;
retval.ptr = ptr;
return retval;
}

template<typename AccessType, typename IndexType>
void get(const IndexType index, NBL_REF_ARG(AccessType) value)
{
bda::__ptr<T> target = ptr + index;
value = target.template deref().load();
}
template<typename AccessType, typename IndexType>
void set(const IndexType index, const AccessType value)
{
bda::__ptr<T> target = ptr + index;
return target.template deref().store(value);
Comment on lines +108 to +115

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think our bda::__ptr needs a load/store method with make available and visible (+ non private)

Its only atomics where avail and vis + non-privateness is implied

}

T atomicMax(const uint64_t index, const T value)
{
bda::__ptr<T> target = ptr + index;
return glsl::atomicMax(target.template deref().ptr.value, value);
}
T atomicExchange(const uint64_t index, const T value)
{
bda::__ptr<T> target = ptr + index;
return glsl::atomicExchange(target.template deref().ptr.value, value);
}

void memoryBarrier()
{
spirv::memoryBarrier(spv::ScopeDevice, spv::MemorySemanticsAcquireReleaseMask | spv::MemorySemanticsUniformMemoryMask);
}

bda::__ptr<T> ptr;
};

struct WorkgroupCounter
{
static WorkgroupCounter create(const uint64_t addr)
{
WorkgroupCounter retval;
retval.ptr = bda::__ptr<uint32_t>::create(addr);
return retval;
}

uint32_t atomicAdd(const uint64_t index, const uint32_t value) // TODO: maybe it should be just increment
{
bda::__ptr<uint32_t> target = ptr + index;
return glsl::atomicAdd(target.template deref().ptr.value, value);
}

bda::__ptr<uint32_t> ptr;
};

static ScratchProxy arithmeticAccessor;

template<class Binop, class device_capabilities>
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<config_t::VirtualWorkgroupSize,config_t::ItemsPerInvocation_0>;
data_proxy_t dataAccessor = data_proxy_t::create(pc.pInputBuf, pc.pOutputBuf[Binop::BindingIndex]);

using reduce_proxy_t = ReduceAccessor<otype_t>;
bda::__ptr<otype_t> ptr = bda::__ptr<otype_t>::create(pc.pReduceBuf);
reduce_proxy_t reduceAccessor = reduce_proxy_t::create(ptr);

WorkgroupCounter wgCounter = WorkgroupCounter::create(pc.pWgCounterBuf);

OPERATION<config_t,binop_base_t,device_capabilities>::template __call<data_proxy_t, ScratchProxy, reduce_proxy_t, WorkgroupCounter>(dataAccessor,arithmeticAccessor,reduceAccessor,wgCounter);
// we barrier before because we alias the accessors for Binop
arithmeticAccessor.workgroupExecutionAndMemoryBarrier();
}
};


template<class Binop>
static void subtest()
{
assert(glsl::gl_SubgroupSize() == config_t::SubgroupSize)

operation_t<Binop,device_capabilities> func;
func();
}

void test()
{
// subtest<arithmetic::bit_and<uint32_t> >();
// subtest<arithmetic::bit_xor<uint32_t> >();
// subtest<arithmetic::bit_or<uint32_t> >();
subtest<arithmetic::plus<uint32_t> >();
// subtest<arithmetic::multiplies<uint32_t> >();
// subtest<arithmetic::minimum<uint32_t> >();
// subtest<arithmetic::maximum<uint32_t> >();
}

[numthreads(config_t::WorkgroupSize,1,1)]
void main()
{
test();
}
Loading