rem This project demonstrates compute shaders as well as the new buffer resources introduced with
rem version 0.4 of the Ziggurat (DX11) plugin.
rem Compute shaders are general purpose programs that are executed on one or several threads on the GPU 
rem (ie. they are not part of the rendering pipeline like all other shader types).
rem Buffers are a means of communicating between these compute shader programs and the CPU, however they
rem can also be used to provide information between different compute shaders or other shader types as well.
rem Compared to constant buffers, these shader resource buffers have no direct upper size bounds and can be as
rem large as required (assuming they fit into VRAM; as a matter of fact they can exceed the available VRAM size, 
rem but in that case they will be paged from RAM which will cause major slowdowns if it has to be done for 
rem each frame). It varies between different graphics cards but you can generally assume that constant buffers can 
rem be read slightly faster by the GPU than these kinds of buffer resources. 
rem Essentially you can think of shader resource buffers as textures; they are quite similar (they even share the
rem same GPU registers!) but while textures are used to hold colour data, buffers can hold arbitrary data.
rem In this example we will use a couple of different buffers to implement a particle system essentially purely
rem on the GPU: I have purposefully made design choices that make this program unable to run on DX10 hardware. 
rem This was done for two reasons; 
rem     1) It makes more sense like this, ie. the necessary code is simpler than it would be otherwise.
rem     2) We can see how both the Append and Consume versions of the append/consume buffer are used simultaneously.
rem
rem In order to get the same effect on DX10 hardware we would have to set the consume (input) buffer to be a normal
rem read-only structured buffer instead; we are making the current number of elements available to other shaders through 
rem a constant buffer and the same could be done with the input particle buffer to avoid having more than one read/write 
rem buffer at any one time. If we did this we would have to manually reset the element count of the particle buffer to 
rem zero before each dispatch of the Update shader program because we cannot swap the input and output append/consume 
rem buffers like we're doing in this (DX11-only) example. We are also making use of the DX11-exclusive ARRAY BUFFER 
rem resource. On DX10 hardware we could replace this by either a single-element STRUCTURED BUFFER or a BYTE ADDRESS BUFFER.

rem Change these if they're too large / have the wrong aspect ratio for your screen
#constant SCREEN_WIDTH          1600
#constant SCREEN_HEIGHT         900
rem This is the maximum number of particles that can exist at any one time.
rem Try to make sure that your emission rate won't manage to create more than this number before the particles time out
rem and get deleted by the Update shader program.
rem The bitonic particle sorting algorithm (see comments around the BITONIC_BLOCK_SIZE constant declaration below) imposes 
rem certain limitations on the maximum number of particles, namely that it must be an even power of two in the range
rem (BITONIC_BLOCK_SIZE * TRANSPOSE_BLOCK_SIZE) .. (BITONIC_BLOCK_SIZE * BITONIC_BLOCK_SIZE). With the default values for
rem those constants given in this example, that means the maximum number of particles will be at least 8192 and at most 262144.
#constant MAX_NUM_PARTICLES     32768
rem How many new particles to emit each second.
rem Each particle will be assigned a lifetime of between 15 and 20 seconds (see the Emit function in ParticleSystem.hlsl); you 
rem should ensure that it won't be possible for the system to create more than the MAX_NUM_PARTICLES defined above in this time frame.
#constant EMISSION_FREQUENCY    1600
rem We will provide this many pseudo-random numbers in the 0..1 range to our shader programs through a buffer (see below)
#constant NUM_RANDOM_NUMBERS    8192
rem We are using a bitonic sorting network to sort our particles by depth for each frame. This is necessary to get them to draw
rem correctly with transparency, since their individual order in the particle buffer is in no particular sorted order and is moreover
rem prone to change between frames as we don't care for thread synchronization when moving particles from the input to the output 
rem buffer when updating their states.
rem The bitonic sort algorithm (http://www.iti.fh-flensburg.de/lang/algorithmen/sortieren/bitonic/bitonicen.htm) requires that 
rem the number of items to sort is an even power of two, so therefore we should ensure that our MAX_NUM_PARTICLES constant 
rem set above is 2^x.
rem Because this sorting algorithm is comparison based it will be bottlenecked by memory access speed. We gain significantly faster 
rem memory access by performing the comparisons in «groupshared memory» on the GPU compared to doing it directly in the data buffer.
rem The size of groupshared memory blocks is limited however; on DX10 hardware it is no larger than 16Kb per group, while DX11 hardware 
rem affords us 32Kb per group. Evidently this means we cannot store very much information in there.
rem Furthermore shader model 4 (DX10) allows at most 768 threads per thread group, while shader model 5 (DX11) allows 1024. This means 
rem that for the greatest versatility in the number of items to sort (the bitonic sorting algorithm is easiest to implement in such a manner
rem that one thread performs a single comparison) we need to break the full list of numbers to sort into individual parts that are sorted by 
rem separate thread groups.
rem In order to facilitate this, the data to be sorted (a simple list of indices into the current particle buffer that are sorted back-to-front
rem based on the Z-depth in view space) is presented to our sorting shader program as a 2D square matrix. This allows us to transpose the 
rem entire offset buffer between several invocations of the bitonic sort shader program and sort smaller sections that are then eventually 
rem brought into place within the buffer as a whole.
rem As such, the BITONIC_BLOCK_SIZE is the number of elements processed by a single thread group of the bitonic sort shader program.
rem In other words, this is the number of threads in a thread group. Thus if you wish to change this, you must also change the corresponding 
rem value in the shader source. As described above, this must be a multiple of two. We could set it to 1024 since we are using DX11 anyway, but
rem the sorting algorithm can work on DX10 hardware as well without any changes (except changing the array buffers used to hold the sorted particle 
rem buffer indices to structured buffers or byte address buffers as previously mentioned since array buffers are DX11-exclusive) if we keep within 
rem its limitations. As such we set the block size to 512 here, which is the largest multiple of two that fits within its maximum of 768 threads 
rem per group.
#constant BITONIC_BLOCK_SIZE    512
rem Like the BITONIC_BLOCK_SIZE, the TRANSPOSE_BLOCK_SIZE corresponds to the number of threads per group used by the matrix transpose shader 
rem program. However, this compute shader will view the data as a square 2D matrix as mentioned above and will have 
rem TRANSPOSE_BLOCK_SIZE * TRANSPOSE_BLOCK_SIZE actual threads, with TRANSPOSE_BLOCK_SIZE threads in the X dimension, and as many in the Y 
rem dimension (see the shader source).
#constant TRANSPOSE_BLOCK_SIZE  16
rem The whole data set will be viewed as a matrix where each row consists of BITONIC_BLOCK_SIZE elements and there are as many rows as required for
rem it to contain MAX_NUM_PARTICLES elements. As long as the block size and number of particles are integer powers of two this integer division will 
rem yield the correct results. We're using globals instead of constants here so that they can derive from the constants declared above, however
rem no part of the program should ever change the value of these globals.
global MATRIX_WIDTH             as dword
global MATRIX_HEIGHT            as dword
MATRIX_WIDTH                    =  BITONIC_BLOCK_SIZE
MATRIX_HEIGHT                   =  MAX_NUM_PARTICLES / BITONIC_BLOCK_SIZE


rem Ensure that DX11Constants.dba is included
INCLUDEGUARD DX11_CONSTANTS

rem Initialize the Ziggurat plugin
dx11 init SCREEN_WIDTH, SCREEN_HEIGHT, false, "DX11 Plugin example: GPGPU Particles", DXVERSION_11_0


rem Our particle system will essentially be completely GPU driven.
rem The first thing we need are a pair of buffers that will represent the current and next state of all particles in existance.
rem We use the special append/consume buffer for this because of the way we need to access the data; an append/consume buffer 
rem implements stack-like behaviour that is safe over multiple concurrent GPU threads. As such we can write a system where each 
rem particle is managed by a single thread; when updating the state of a particle it will then pop ("consume") a single 
rem particle from the input buffer (which is also the output from the previous update pass), update its contents and, unless the 
rem particle should be destroyed (such as because it has existed for a certain amount of time or whatever other destruction criterion
rem you might have), pushes ("appends") the updated version to the output buffer which is later used to render the particles.
rem Append/consume buffers are special ways of looking at a structured buffer, which means that each element will correspond to 
rem a certain HLSL-defined struct of arbitrary size. Because of this we can store all information we need about a particle (such 
rem as position, velocity, size and colour) in a single element of this kind of buffer. Since we don't need sequential access to
rem the particles, we don't need to synchronize in what order we append / consume them from their individual buffers.
rem We will need to store a position (3 floats), a velocity (3 floats), the view space depth (1 float), a particle size (1 float),
rem  a colour (3 floats), and a timeout timestamp (1 uint) per particle, thus our element size for the particle buffers
rem will be 48 bytes. This corresponds to the «Particle» struct declared in "shader/Declarations.hlsl" which is included by the 
rem other shader files.
bufParticleIn   = dx11 create append consume buffer(MAX_NUM_PARTICLES, 48)
bufParticleOut  = dx11 create append consume buffer(MAX_NUM_PARTICLES, 48)
rem As stated above, the particle elements may change order randomly depending on which GPU threads finishes first in the above 
rem buffers. But in order to properly render semi-transparent particles we will have to sort them so that the ones furthest 
rem away from the camera will be drawn first. We could use the structured buffer view of the particle buffers and sort directly 
rem in those but we're using two other lists that merely sort offsets into those buffers instead. Since this requires sorting a 
rem much smaller buffer one can theorize it will waste less bandwidth than copying the full particle data elements around. It also
rem maps better into the groupshared GPU cache memory where the actual sorting comparisons will be carried out.
rem We use the array buffer here which is a simple buffer exclusive to DirectX 11; it basically corresponds to an array of a 
rem primitive datatype such as bool, int, uint or float. Syntactic sugar datatypes like float3 or float4x4 are «not» supported 
rem by the array buffer; if you need to store these you should use a structured buffer instead. On non-DX11 hardware a 
rem structured buffer with a single element or a byte address buffer could be used instead of an array buffer.
rem In order for a buffer to be writable by the GPU it needs to have the appropriate ACCESSMODE flag set. The append/consume 
rem buffers used above can only ever be used as writable resourcse so we don't need to explicitly state the GPUWRITABLE 
rem access mode for those.
rem Like above we are creating two buffers identical buffers for sorting the offset indices into the particle buffer. This makes
rem it a lot easier to transpose our partial sorting passes input data matrices from one buffer to the other on the shader side.
bufIndexLookup  = dx11 create array buffer(MAX_NUM_PARTICLES, DXGI_FORMAT_R32_UINT, ACCESSMODE_GPUWRITABLE)
bufIndexLookup2 = dx11 create array buffer(MAX_NUM_PARTICLES, DXGI_FORMAT_R32_UINT, ACCESSMODE_GPUWRITABLE)
rem There is no built-in way to generate pseudo-random numbers on the GPU.
rem We can however create a buffer with evenly distributed floating point values in the 0..1 range and then jumble them around.
rem The individual GPU threads can then index into this buffer using their thread id's added to a CPU-generated random offset 
rem that changes each frame for generating seemingly random values.
rem We don't need to update this buffer from the GPU, nor will the CPU continously change its content after its creation so
rem we use the default access mode.
bufRandom       = dx11 create array buffer(NUM_RANDOM_NUMBERS, DXGI_FORMAT_R32_FLOAT, ACCESSMODE_DEFAULT)
rem Finally we will need a pair of indirect buffers.
rem These are a means of calling certain DirectX functions where the arguments of the functions are determined by shader programs.
rem Reading GPU-generated data back to the CPU is a pretty expensive operation (because it will have to synchronize the GPU and CPU) 
rem that is best avoided to do on a per-frame basis if possible. Using indirect buffers we can do things like have one shader program 
rem determine how many threads to dispatch for another shader program, or how many times to invoke the vertex shader for a 
rem "null mesh" (see further description of that below).
rem An indirect buffer essentially contains the argument list used to call such a DirectX function.
rem Like append/consume buffers, indirect buffers are always GPU-writable.
rem This one will be used to dispatch the Update shader program that will update our particles. As such its number of threads
rem will have to be derived from how many particles are currently in existance. This isn't known on the CPU side and as such we use an 
rem indirect buffer for providing this information. The buffer contents corresponds to the argument list of the DX11 DISPATCH COMPUTE SHADER 
rem function, which takes 3 uints.
bufUpdateArgs   = dx11 create indirect buffer(12)
rem This indirect buffer is used to tell the drawcall of our particle mesh how many particles ("vertices") to draw. No vertex data is bound 
rem to the rendering pipeline but instead our vertex and geometry shaders will build camera-facing quads from each particle element in the
rem input buffer. The drawcall function expects four uint arguments which will fit into a 16-byte buffer.
bufDrawArgs     = dx11 create indirect buffer(16)

rem Fill out the random number buffer
for n = 0 to NUM_RANDOM_NUMBERS - 1
    dx11 set buffer float bufRandom, n, n / (NUM_RANDOM_NUMBERS - 1.0)
next n
rem Shuffle the contents around so that they will be in a "random" order
for pass = 0 to 4
    for n = 0 to NUM_RANDOM_NUMBERS - 1
        offset = rnd(NUM_RANDOM_NUMBERS - 1)
        value# = dx11 get buffer float(bufRandom, n)
        dx11 set buffer float bufRandom, n, dx11 get buffer float(bufRandom, offset)
        dx11 set buffer float bufRandom, offset, value#
    next n
next pass

rem Fill out indirect draw arguments buffer
dx11 set buffer dword bufDrawArgs, 0,   0   ` Number of vertices (will be overwritten with the number of particles on a per-frame basis prior to rendering)
dx11 set buffer dword bufDrawArgs, 4,   1   ` Number of times to instance the vertices. If set to 0 they won't be processed at all.
dx11 set buffer dword bufDrawArgs, 8,   0   ` Offset into the (non-existing) vertex buffer
dx11 set buffer dword bufDrawArgs, 12,  0   ` Offset into the (non-existing) instance buffer

rem We will also need a number of constant buffers for providing various small amounts of context-dependent data to our shaders.
rem The first one is used to contain the current number of elements in the particle buffer.
rem It isn't possible to get the "currently used elements count" of an append/consume buffer directly from HLSL, however we can
rem perform a VRAM memcopy-style operation that will put it in another buffer resource (or a constant buffer as we do here) without 
rem the need to waste time by reading the value back to the CPU.
cbParticleCount     = dx11 create cbuffer(4, false)     ` We won't write to this from the CPU so making it dynamic has no value whatsoever
rem The Emit and Update shader programs will need to know the current time, a delta time (this could be calculated on the GPU by storing the 
rem time from the last frame but doing that would require yet another buffer for persistent storage so we might as well provide this value 
rem pre-calculated from the CPU) and the world-view matrix of the object representing the particle object. The reason for this is 
rem that we will need to know the depth of the individual particles in view-space before rendering them so that we can properly sort them
rem in a back-to-front fashion. Thus we will need a 72-byte cbuffer for holding this information. However, constant buffers are required to always
rem be an even multiple of 16 bytes in size for efficient access patterns on the GPU which is capable of reading 128-bit words in a single instruction.
rem This is handled behind the scenes by the Ziggurat engine such that is you were to specify a cbuffer size of 4 it will be automatically scaled up to 16 bytes;
rem in the same way a requested size of 72 bytes would actually allocate an 80 byte cbuffer. As we will see further below we will need to write to the
rem last bytes of this cbuffer to store a world-view matrix in it (which aren't allowed to break a 16-byte border in the buffer; see more details below) and 
rem for this reason we explicitly ask for a cbuffer of 80 bytes to be created here, so that there will be no confusion when it is written to past the 72 bytes.
cbFrameData         = dx11 create cbuffer(80, true)     ` This will be updated by the CPU on a per-frame basis so it will benefit from being made dynamic
rem The Emit shader program will need to know how many new particles to emit when called. 
rem It will also need a random seed value into the random buffer declared above in order that it might emit the new particles with random attributes.
rem Naturally this information will be provided using a constant buffer as well.
cbEmissionData      = dx11 create cbuffer(8, true)      ` While not necessarily updated by the CPU for each frame, it will indeed update it a lot
rem We also provide some scene-static settings to the Emit shader program, namely the locations in world-space of four different emission positions.
rem When emitting a particle it will be randomly centered around one of these points.
cbEmissionSettings  = dx11 create cbuffer(64, false)    ` This is only set once during setup whereafter the CPU will not alter the contents at all
rem To demonstrate that we can do interactive things with compute shaders we will attract all particles towards the mouse cursor when the left mouse 
rem button is held down. In order to facilitate this we will need a point of attraction in world space as well as a force factor to be provided to
rem the compute shader. This may be updated in each frame if the left mouse button is held down, so we'll store this data in a dynamic constant buffer.
cbForceData         = dx11 create cbuffer(16, true)
rem This is used to provide arguments to our bitonic sort and matrix transpose compute shaders
cbSortData          = dx11 create cbuffer(16, true)     ` This will be updated several times per frame by the CPU so it makes sense to make it dynamic
rem Finally our rendering shader technique will need to know the world-space position of the rendering camera so that all particles can be made to 
rem face it at all times. It will also be interested in the current timestamp so that we can fade particles out in the pixel shader as they approach
rem their timeouts.
cbRenderData        = dx11 create cbuffer(16, true)     ` This will have to be updated on a per-frame basis

rem Fill out the emission settings cbuffer.
rem The temporary vectors created here will be valid until the next call to DX11 SYNC when they are released.
rem This is quite OK because we are merely using them as a shortcut to copy multiple values to our constant buffer in a single step.
dx11 set cbuffer vector3 cbEmissionSettings, 0,     dx11 create temp vector3(-95, 0, -95)
dx11 set cbuffer vector3 cbEmissionSettings, 16,    dx11 create temp vector3(95, 0, -95)
dx11 set cbuffer vector3 cbEmissionSettings, 32,    dx11 create temp vector3(-95, 0, 95)
dx11 set cbuffer vector3 cbEmissionSettings, 48,    dx11 create temp vector3(95, 0, 95)

rem Load compute shader programs.
csUpdate                = dx11 load compute shader("shader/ParticleSystem.hlsl",    "Update")
csEmit                  = dx11 load compute shader("shader/ParticleSystem.hlsl",    "Emit")
csComputeUpdateArgs     = dx11 load compute shader("shader/ParticleSystem.hlsl",    "ComputeIndirectUpdateArgs")
csBitonicSort           = dx11 load compute shader("shader/ParticleSort.hlsl",      "BitonicSort")
csMatrixTranspose       = dx11 load compute shader("shader/ParticleSort.hlsl",      "MatrixTranspose")
csResetOffsetBuffer     = dx11 load compute shader("shader/ParticleSort.hlsl",      "ResetOffsetBuffer")

rem Associate buffer resources and constant buffers for use with our shader programs
rem *** Constant buffers ***
dx11 set compute shader cbuffer csUpdate, 0, cbFrameData
dx11 set compute shader cbuffer csUpdate, 1, cbParticleCount    ` The Update shader needs to know this so that it won't consume past the end of the input buffer
dx11 set compute shader cbuffer csUpdate, 2, cbForceData

dx11 set compute shader cbuffer csEmit, 0, cbFrameData
dx11 set compute shader cbuffer csEmit, 1, cbEmissionData
dx11 set compute shader cbuffer csEmit, 2, cbEmissionSettings

rem This compute shader only needs the cbuffer containing the particle count. Since it is already declared as being bound to register b1 in the 
rem ParticleSystem.hlsl file (from where the Update shader program uses it) we might as well bind it to stage 1 here as well.
rem We could set it to stage 0 but then we would have to provide another declaration in the HLSL source; also it wouldn't be able to share the 
rem same name ("BufferSize"). Of course this could also be handled by putting each shader program into its own source file, however since the 
rem ComputeIndirectUpdateArgs function is as simple and as tied-in with the other particle-managing shader programs as it is I chose not to.
dx11 set compute shader cbuffer csComputeUpdateArgs, 1, cbParticleCount

rem Constant buffers used by the depth sorting shader programs
dx11 set compute shader cbuffer csBitonicSort,          0, cbSortData
dx11 set compute shader cbuffer csBitonicSort,          1, cbParticleCount  ` We need to do this to move any out-of-bounds indices to the end of the sorted offset list
dx11 set compute shader cbuffer csMatrixTranspose,      0, cbSortData
dx11 set compute shader cbuffer csResetOffsetBuffer,    1, cbParticleCount  ` Re-use the same cb register as the bitonic sort program does that is declared in the same shader source file

rem *** Read-only shader resource buffers ***
dx11 set compute shader buffer csUpdate,    0, bufRandom
dx11 set compute shader buffer csEmit,      0, bufRandom

rem *** Read/write shader resource buffers ***
dx11 set compute shader rwbuffer csComputeUpdateArgs,   0, bufUpdateArgs

rem The remaining buffers will be set in-place in the main loop since they are swapped between dispatches as described above.


rem We need to load shaders and set up a shader technique to render the particles as well.
rem This will be used to index into the (sorted) particle buffer per vertex id (see the vertex shader source for further details) and will expand 
rem each such vertex id into a camera-aligned quad representing a single particle. This means that all geometry data is created entirely on the 
rem GPU and we don't need to provide any vertex buffer (hence why a "null mesh" is used below).
vs = dx11 load vertex shader("shader/Particle.fx", "VS")
gs = dx11 load geometry shader("shader/Particle.fx", "GS")
ps = dx11 load pixel shader("shader/Particle.fx", "PS")
techParticle = dx11 create shader technique(nullptr, vs, ps, gs)
rem As mentioned above the geometry and pixel shaders will need to know the current world-space position of the camera as well as the current timestamp, 
rem so we associate the render data constant buffer to the shader technique for providing that information.
dx11 add constant buffer techParticle, cbRenderData
rem There will be visual artifacts where two semi-transparent particles intersect each other.
rem In order to avoid this (since the particles are sorted by depth before being rendered anyway) we disable the z-write for the particles so 
rem that they will not write to the depth buffer (they will still read from it which means they will be properly clipped when behind other objects though).
rem To do this we create a custom depth stencil state and associate it with our particle-drawing shader technique.
dss = dx11 create depth stencil state()
dx11 set depth write mask dss, D3D11_DEPTH_WRITE_MASK_DISABLED
dx11 set depth stencil state techParticle, dss


rem Create an object to visually represent our particles.
rem We are using the special "null mesh" here. This is a special type of "mesh" that doesn't hold any actual vertex data. Instead it can essentially be 
rem thought of as a means to invoke the associated vertex shader as many times as its "vertex count" is set to. This vertex count can be changed on the 
rem fly during runtime and can even be indirectly set using an indirect buffer (as we will do here), thus bypassing CPU intervention in determining how 
rem many "vertices" to generate. This of course means that we cannot count on any vertex data to be supplied to the vertex shader. What we can use is 
rem system values, such as the vertex id (requested by giving the vertex shader an input argument with the semantic SV_VertexId). This will increase from 
rem zero and up to (numVertices - 1) for each invocation of the vertex shader and can thus be used to index into our particle buffer for drawing one particle 
rem per "vertex" in the null mesh (the "vertices" are handled like control points and are expanded into the quads needed to render a particle using the 
rem geometry shader). See «Particle.fx» for implementation details of the particle rendering shaders.
rem Take note that we need to set the bounds of the null mesh manually since the Ziggurat engine cannot figure this out on itw own due to there being no 
rem vertex data. If this bounding box falls outside the rendering camera's viewing frustum the entire particle object will be clipped from rendering 
rem so it is important to ensure that the entire particle system will fit within the bounding box.
rem Since we are going to see each "vertex" as a particle (to be expanded by the geometry shader) we want the pointlist topology.
rem Also we set the initial vertex count to 0 since we will use the indirect buffer created above to instruct how many vertices (particles) to draw in each frame.
meshParticle = dx11 create null mesh(0, -100, -100, -100, 100, 100, 100, TOPOLOGY_POINTLIST)
objParticle  = dx11 create object(meshParticle)
rem Associate the indirect draw argument buffer with the particle mesh; this will cause it to use that for issuing its draw calls.
rem Take note that this function only works with null meshes.
dx11 set null mesh indirect draw argument buffer meshParticle, bufDrawArgs
rem And set it to be rendered using our shader technique defined above.
dx11 set object shader technique objParticle, techParticle

rem We also need a texture for our particles. The particle pixel shader will blend this grayscale / alpha texture with the individual particle colours so 
rem we can set just this one texture for the entire particle object.
imgParticle = dx11 load image("media/Particle.png")
dx11 set object texture objParticle, imgParticle
dx11 set object position objParticle, 0, 0, 0

rem Finally, mark the particle object as being transparent.
rem This will make sure it is rendered in a separate pass after any opaque geometry and as such will allow the opaque geometry to occlude the particles.
rem We can use the cheap transparency mode since we have no other transparent objects in the scene besides the particles that they will need to be alpha 
rem blended with.
dx11 set object sort mode objParticle, DX11_SORTMODE_TRANSPARENT_CHEAP


rem Create a floor plane. This is opaque and as such should occlude any particles that are behind it (hence the sort mode setting above).
objFloor = dx11 create object quad(200, 200)
dx11 set object position objFloor, 0, 0, 0
dx11 set object angle objFloor, 90, 0, 0
imgCheckerboard = dx11 load image("media/Checkerboard.png")
dx11 set object texture objFloor, imgCheckerboard
dx11 set object texcoord scaling objFloor, 5, 5


rem Obtain a reference to the main camera and perform some setup on it.
cam = dx11 get main camera()
dx11 set camera range cam, 1, 600.0
dx11 set camera backdrop cam, 0xffa0a0a0
dx11 set camera position cam, 0, 50, -250
dx11 set camera lookat cam, 0, 0, 0

rem We will attract the particles towards a point in space 250 units in front of the main camera when the left mouse button is held down
rem (this happens to be the Z distance to the (0, 0, 0) scene origin from the main camera using the above positioning).
rem We can pre-calculate this distance in screen (projection) space since that is from where we will do our raycasting to make sure the
rem attractor position is where the mouse is pointing.
attractorDist# = dx11 get vector3 z(dx11 transform vector3 coord(dx11 create temp vector3(0, 0, 250), dx11 get projection matrix(cam)))


rem We use these variables to implement a timer based movement system in order to ensure that all movement is smooth over varying frame rates.
lastTick        as double integer
currentTick     as double integer
lastEmission    as double integer
currentTick     =  dx11 get high resolution tick()  ` If we leave this at zero the initial frame will have an extremely high delta value which won't look good
lastEmission    =  currentTick                      ` This is used to keep track of when we need to call the Emit shader program to emit new particles

rem Main loop
while not dx11 quit()
    rem Update timer
    engineTick  = dx11 get engine tick()
    lastTick    = currentTick
    currentTick = dx11 get high resolution tick()
    delta#      = dx11 get high resolution delta time(lastTick, currentTick)
    

    rem Attract particles towards a point 250 units («attractorDist#») in front of the camera, projected from the mouse cursor position should 
    rem the left mouse button be pressed.
    if dx11 is mouse button down(0)
        rem Compute inverse view projection matrix for transforming coordinates from screen space to world space
        matInverseViewProj = dx11 get matrix inverse without determinant(dx11 multiply matrix(dx11 get view matrix(cam), dx11 get projection matrix(cam)))
        
        rem Compute attractor position by raycasting «attractorDist#» units into the screen from the current mouse coordinates
        sx# = ((2.0 * dx11 get mouseX()) / SCREEN_WIDTH) - 1.0  ` Compute screen space coordinates
        sy# = 1.0 - ((2.0 * dx11 get mouseY()) / SCREEN_HEIGHT) ` from the mouse cursor position
        
        vecScreenPos = dx11 create temp vector3(sx#, sy#, attractorDist#)
        vecWorldPos  = dx11 transform vector3 coord(vecScreenPos, matInverseViewProj)
        
        rem Update force data constant buffer with the attractor world position and the attraction force
        dx11 set cbuffer vector3 cbForceData, 0, vecWorldPos
        dx11 set cbuffer float cbForceData, 12, 10.0
    else
        rem If the mouse button was previously held down and just released in this frame we should reset the attractor force to zero to stop it from 
        rem influencing the particles. For an added effect we will set the attractor position to have a very high repelling force for a single frame
        rem before being reset to zero. This will cause all particles to scatter outwards from the hitherto attracting position when the LMB is released.
        if dx11 is mouse button released(0)
            dx11 set cbuffer float cbForceData, 12, -20000.0
            bResetForce = true
        else
            if bResetForce
                dx11 set cbuffer float cbForceData, 12, 0.0
                bResetForce = false
            endif
        endif
    endif
    
    rem Allow toggling particle emissions by pressing the 'E' key
    if dx11 is key hit(DIK_E) then isEmissionHalted = true - isEmissionHalted  ` Since the NOT operator is bitwise rather than logic in DBPro that won't work here
    
    
    rem ************************************************************************
    rem * Start updating particle system
    rem ************************************************************************
    
    rem Update the per-frame constant buffer
    dx11 set cbuffer dword cbFrameData, 0, engineTick               ` This is a millisecond tick; the double integer ticks above are higher resolution and won't fit
    dx11 set cbuffer float cbFrameData, 4, delta#                   ` We can still use the high resolution-derived delta time since it is a normal 32-bit float
    rem We use a temporary matrix to compute the current world-view matrix for the particle system.
    rem The fact that we provide this here means that any camera or particle system transformation (movement / rotation / scaling) needs to be done BEFORE 
    rem this point in the main loop or the shader won't have access to the latest values and will thus potentially render incorrectly.
    rem Here we also see an important quirk of constant buffers; no single primitive element (including vectors and matrices) is allowed to pass a 16-byte boundary.
    rem What this essentially means is that we cannot store for example a float3 at offset 8 in a cbuffer since it would then range from bytes 8 through 20, which
    rem passes over 16. In the same way it cannot be stored at offset 24 because it would then break the 16-byte border at 32 bytes and so on.
    rem The reason for this is that the GPU drivers will read vector data as 4-float vectors (128 bits) simultaneously and for allowing the best access speed optimizations
    rem data accesses are limited to 16-byte denominators. In the case of a 4x4 matrix as what we have here, the GPU will read it as 4 sets or float4's so the matrix itself
    rem isn't considered to "break" any 16-byte borders even though it requires 64 bytes to store. While it is possible to store the 4x4 matrix here at offset 8 in the
    rem constant buffer, the HLSL code will automatically interpret a struct containing { uint; float; float4x4; } in such a way that the float4x4 is read from address 16 so
    rem this would yield an incorrect matrix to be read on the GPU and thus cause some pretty hard-to-find bugs, so this is a good thing to be aware of.
    rem In essence this means we will have 8 bytes of unused padding in our constant buffer. Keep in mind that this is always the case anyway since cbuffers can only be 
    rem allocated to be an even multiple of 16 bytes in size; the Ziggurat engine internally scales all cbuffer sizes that fail this requirement up to the next such multiple
    rem of 16 behind the scenes. For this reason there is no point in putting the matrix at the top of the cbuffer instead; this would just move the padding to the end of
    rem the buffer. Of course access from the DBPro side would seem more linear though, which could be considered enough of a reason to make it so, but I wanted to bring this
    rem issue to attention here.
    matWV = dx11 multiply matrix(dx11 get object world matrix(objParticle), dx11 get view matrix(cam))
    dx11 set cbuffer matrix cbFrameData, 16, matWV
    
    rem Send camera position data, along with the current engine tick (for fading the alpha of particles approaching their timeout tick), to the shaders
    dx11 set cbuffer float cbRenderData, 0,  dx11 get camera position x(cam)
    dx11 set cbuffer float cbRenderData, 4,  dx11 get camera position y(cam)
    dx11 set cbuffer float cbRenderData, 8,  dx11 get camera position z(cam)
    dx11 set cbuffer dword cbRenderData, 12, engineTick
    
    rem Swap the input and output particle buffers.
    rem This will make it so that the element count of the output buffer from the previous frame gets consumed back down to zero while the input buffer from 
    rem the previous frame (where it got consumed down to 0 elements) will again have elements added to it.
    bufParticleTmp = bufParticleOut
    bufParticleOut = bufParticleIn
    bufParticleIn  = bufParticleTmp
    rem We also need to (re-)bind the swapped buffers to the shaders using them...
    rem     (note that we're not using the DX11 SET COMPUTE SHADER RWBUFFER function here; if we did that the append/consume buffer would be bound as a normal
    rem     structured buffer to the shader)
    dx11 set compute shader append consume buffer csUpdate,  0, bufParticleOut
    dx11 set compute shader append consume buffer csUpdate,  1, bufParticleIn
    dx11 set compute shader append consume buffer csEmit,    0, bufParticleOut
    rem ... As well as to the object used to render the particle system.
    rem     (Here we bind the output particle buffer as a read-only buffer; in this case it will also appear as a normal structured buffer to the shader(s) using it).
    dx11 set object buffer objParticle, bufParticleOut, 0
    
    rem Now we dispatch our first compute shader program.
    rem This is a very simple shader program that will run on a single thread and look at the input particle count set to the cbuffer above and fill out the bufUpdateArgs 
    rem buffer accordingly to determine how many thread groups we need to dispatch the Update compute shader on in order to have at least one thread per particle.
    rem As mentioned above there are 64 threads per thread group.
    rem The reason we do this using another compute shader is once again to bypass having to read data back from the GPU to the CPU, which requires synchronization
    rem and thus results in decreased execution speed.
    rem It should also be mentioned that the division into three dimensions for threads and thread groups is built into DirectX and is intended to make it easier to 
    rem break down multi-dimensional problems. For this application we don't need anything but a 1D-array based on the particle count however so we only use the X 
    rem dimension and will always set the remaining Y and Z dimensions to 1.
    dx11 dispatch compute shader csComputeUpdateArgs, 1, 1, 1
    
    rem Now that the above shader has determined how many thread groups we need for the Update shader program we can dispatch it on that many groups.
    rem This shader program will consume particles from the previous state (input buffer) and determine if they are to be removed; if not they will be 
    rem updated by applying gravitational effects to their velocity and then applying that to their current position, whereafter the updated particle is 
    rem added to the output buffer. Any particle that is to be removed is simply not added to the output buffer.
    rem As such the input buffer will be empty and the output buffer contain the current state of the particle system once this shader program finishes.
    dx11 dispatch compute shader indirect csUpdate, bufUpdateArgs
    
    rem We emit any new particles after updating the shader system. This will append newly emitted particles to the end of the output buffer.
    rem Depending on the emission frequency (see the constant defined above) and the current frame rate it is possible we will need to emit either none, 
    rem one or several new particles in each frame. Determine this by timing unless the «isEmissionHalted» flag is set, in which case we emit no new particles at all.
    if isEmissionHalted
        deltaEmission# = 0.0
        rem Reset lastEmission time to the current frame to prevent emissions from building up until the «isEmissionHalted» flag is set to false again
        lastEmission   = currentTick
    else
        deltaEmission# = dx11 get high resolution delta time(lastEmission, currentTick)
    endif
    
    if deltaEmission# > (1.0 / EMISSION_FREQUENCY)
        emitCount = int((EMISSION_FREQUENCY * deltaEmission#) + 0.5)
        rem The current output particle buffer is already bound to the Emit shader program (see above).
        rem We need to tell it how many particles to emit however (the Emit shader could calculate this itself by keeping track of when it was last invoked 
        rem but that would require another buffer to which it could store that information so it is simpler to just provide this from the CPU side through
        rem a constant buffer since it keeps track of these things to determine whether to at all dispatch the Emit shader program anyway).
        dx11 set cbuffer dword cbEmissionData, 0, emitCount
        rem We also provide a random seed offset value into the random buffer to ensure that the same threads won't get the same "random" values on each 
        rem run. Again, we could solve this by storing the previous seed value on the GPU but permanent storage there requires the use of yet another buffer 
        rem so this is the easier solution.
        dx11 set cbuffer dword cbEmissionData, 4, rnd(NUM_RANDOM_NUMBERS - 1)
        
        rem Dispatch the emit shader program.
        rem Since we know how many new particles to emit we can determine its necessary thread group count on the CPU.
        rem As with the Update shader program we have determined that a single thread group will contain 64 threads and we only use the X dimension
        rem because using more thread dimensions will just make things more complicated rather than easier with our current problem to solve here.
        dx11 dispatch compute shader csEmit, ceil(emitCount / 64.0), 1, 1
        
        rem When we get here any newly emitted particles have been appended to the end of the current output buffer which thus contains the complete 
        rem current state of the particle system in this frame.
        rem Store the current time of emission.
        lastEmission = currentTick
    endif
    
    rem The output particle buffer (that by now holds the current state of the particle system) is already bound to our particle rendering object.
    rem We need to tell our null mesh how many particles to draw however.
    rem We can simply use the "VRAM memcpy" to copy the element count of the output buffer into the correct (first) argument contained in the 
    rem draw arguments buffer rather than writing a separate compute shader just to copy the value over (the reason we need a separate compute shader 
    rem for doing this for the indirect argument buffer used to dispatch the Update shader program is that it is *based* on the element count but not 
    rem a direct copy and the compute shader is thus needed to do the appropriate calculations on it on the GPU).
    rem Note that we never need to change the last three arguments to the null-mesh drawcall contained in the bufDrawArgs buffer.
    dx11 copy append consume buffer element count to buffer bufParticleOut, bufDrawArgs, 0
    
    rem We need to copy the current element count of the output append/consume buffer to another buffer resource since it isn't readily available to the shaders in 
    rem any other way. This copying essentially works like a VRAM memcpy operation and thus bypasses the overhead of having to read the data back to the CPU (which 
    rem might cause stalling on the GPU and CPU alike since they need to be synchronized).
    rem The reason we are doing this here instead of earlier in the loop is that no particle data will exist in the first frame's input buffer anyway. The sorting
    rem compute shaders further use this information as well and require the particle count of the current output buffer(in this frame), whereas the other shader programs
    rem will use it to read the input particle count in the following frame; 
    rem     the Update shader program needs to know the element count to ensure that it won't consume past the end of the input buffer (for which there are no error checks 
    rem provided by HLSL, yet doing so causes undefined behaviour on some (but not all) graphics cards. It would also mess up our following inserts into the buffer in the 
    rem next pass since the counter would go into the negatives and count up from that when we append new elements).
    rem The ComputeIndirectUpdateArgs shader program also needs to know this value to determine how many thread groups to dispatch the actual Update shader program to in 
    rem order to ensure that it will at least have one thread per input particle. The Update shader program still needs to know the input buffer count because each thread 
    rem group is set to contain 64 threads and as such we might have up to 63 threads more than we have actual input particle data, which in that case have to be told to
    rem basically do nothing. 64 threads per group was chosen because it is an even multiple of 32 (which is the actual hardware SIMD group size on NVIDIA cards) and 64
    rem (which is used by AMD cards). You can set the number of threads per group using the [numthreads(x, y, z)] attribute when declaring your compute shaders in HLSL.
    rem Take care that you can only have up to 65535 thread groups in any one dimension however, so if you just set one thread per group you severely reduce how many 
    rem actual threads you can dispatch your shader to. The maximum number of threads per thread group allowed is 1024 on DX11 hardware and 768 on DX10 hardware.
    rem DX10 hardware also only supports the X and Y dimensions for threads and thread groups, while DX11 adds the Z dimension. Thus you must set the Z dimension value 
    rem to 1 (not 0!) whenever writing a compute shader to be used on DX10 hardware. On DX11 hardware, the maximum number of thread groups in the Z dimension is limited
    rem to 64 rather than 65535 as it is for the X and Y dimensions. Also the combined number of thread groups in all dimensions may not exceed 65535.
    dx11 copy append consume buffer element count to cbuffer bufParticleOut, cbParticleCount, 0
    
    rem By now the updated state of all our particles are in the output buffer ready to be rendered.
    rem However, due to the parallel nature of the GPU it isn't possible to determine in what order the particles will be appended to the output buffer.
    rem As such, rendering it like this each frame will cause immense flickering as the draw order will change from frame to frame.
    rem In order to alleviate this problem and to also ensure proper alpha blending where several semi-transparent particles overlap, we need to sort the 
    rem particles so that we can ensure that the furthest from the camera are rendered first and the ones closest last.
    rem We use a GPU implementation of the bitonic sorting network which is seemingly the industry standard for efficient, parallel GPU sorting.
    rem While other, hybrid algorithms can yield better results (see «http://www.cse.chalmers.se/~uffe/hybridsortElsevier.pdf»), the bitonic sort is
    rem suggested to be commonly in use for graphics fragment sorting and similar applications, suggesting it is good enough while being a fair deal simpler
    rem to implement.
    rem Still so the bitonic sort algorithm mapped onto the parallel GPU architecture is quite complicated to grasp and as to keep the main loop simpler I 
    rem have moved its CPU-side control structure to a standalone function.
    rem Put simply, calling this function will ensure that the appropriate indices in the offset buffers will be rearranged such that, when indexed by 
    rem control point ("vertex") id's, the contained indices into the current particle state buffer will be sorted back-to-front. This is necessary since
    rem any alpha blending operations can only blend the current colour being written with that already present at the target pixel in the back buffer.
    DepthSortParticles(bufParticleOut, csResetOffsetBuffer, csBitonicSort, csMatrixTranspose, cbSortData, bufIndexLookup, bufIndexLookup2)
    
    rem The last thing we need to do is provide our sorted offset map buffer to our particle rendering object.
    rem Following that the particle shader technique will draw our particle system when we render it using a camera (such as will happen for the main camera 
    rem when DX11 SYNC is called below with the default sync mask set).
    dx11 set object buffer objParticle, bufIndexLookup, 1
    
    rem ************************************************************************
    rem * Finished updating particle system
    rem ************************************************************************
    
    rem Print informative text
    dx11 draw text 4, 4, "FPS: " + str$(dx11 get screen fps()) + crlf$() + "Emissions: " + boolstr(isEmissionHalted, "[c=0xffff0000]halted", "[c=0xff00b000]active")
    rem Print instructions
    dx11 draw text SCREEN_WIDTH / 2, SCREEN_HEIGHT - 24, "[c=0xffffffb0]Use the left mouse button to attract particles.", true
    dx11 draw text SCREEN_WIDTH / 2, SCREEN_HEIGHT - 8,  "[c=0xffffffb0]Press [c=0xff80ccff][E][c=0xffffffb0] to toggle particle emissions.", true
    dx11 sync
endwhile

end


rem ************************************************************************
rem * Helper functions
rem ************************************************************************
    
rem This function calculates the largest 2-logarithm for the given integer value, such that integer_log2(4) == 2 and integer_log2(5) == 3.
function integer_log2(in)
    rem Special case
    if in = 0 then exitfunction 0
    result = ceil(log(in) / log(2))
endfunction result

rem This function returns one of the two input strings depending on whether the input condition is true or false.
function boolstr(condition as boolean, trueString as string, falseString as string)
    if condition then exitfunction trueString
endfunction falseString

rem This function performs the bitonic depth sorting of our offset map based on particle depths such that the furthest particles from the main camera
rem will be rendered first, and the closest particles to the camera will be rendered last.
rem The parallel sorting algorithm requires several dispatch passes and data transpositions in order to yield a coherent, sorted buffer with a size
rem that exceeds the number of threads in a single thread group (which is set to BITONIC_BLOCK_SIZE).
rem As previously mentioned, the used (bitonic) sorting algorithm requires the data set to sort to be an even power of two. We will thus have it sort
rem as many elements as our maximum number of particles allowed in each frame; this is actually faster than adding control flow statements to the compute 
rem shader programs. In order to ensure that the unused id's (which have no associated particles in the current particle system state) won't interfere 
rem with the rendering, the depth value of these are automatically set to 0 by the bitonic sort shader program, thus ensuring they will appear at the end
rem of the sorted offset list. If there are any actual particles with lesser depths they are clipped by the near plane of the rendering camera and the then
rem technically incorrect ordering of the particles behind the camera won't matter as they aren't visible anyway.
function DepthSortParticles(bufParticleState as dword, csResetOffsetBuffer as dword, csBitonicSort as dword, csMatrixTranspose as dword, cbSortData as dword, bufIndexLookup as dword, bufIndexLookup2 as dword)
    rem Bind current particle state buffer to our bitonic sorting shader program
    dx11 set compute shader buffer csBitonicSort, 1, bufParticleState
    
    rem Reset the offset buffer contents so that we can proceed to sort it
    dx11 set compute shader rwbuffer csResetOffsetBuffer, 0, bufIndexLookup
    dx11 dispatch compute shader csResetOffsetBuffer, MAX_NUM_PARTICLES / BITONIC_BLOCK_SIZE, 1, 1   ` 1 thread per element
    
    rem Sort the rows for the levels <= the block size, where each such level is a power of two starting at 2 and going to the bitonic block size
    level = 2
    while level <= BITONIC_BLOCK_SIZE
        rem Update constant buffer
        dx11 set cbuffer dword cbSortData, 0,  level
        dx11 set cbuffer dword cbSortData, 4,  level
        dx11 set cbuffer dword cbSortData, 8,  MATRIX_HEIGHT    ` The 'width' should be set to the height when sorting rows
        dx11 set cbuffer dword cbSortData, 12, MATRIX_WIDTH     ` as well as the 'height' to the width
        
        rem Sort the row data
        dx11 set compute shader rwbuffer csBitonicSort, 0, bufIndexLookup
        dx11 dispatch compute shader csBitonicSort, MAX_NUM_PARTICLES / BITONIC_BLOCK_SIZE, 1, 1
        
        rem Increment level to the next power of two
        level = level * 2
    endwhile
    
    rem Sort the columns and the rows for the levels > the block size (powers of two starting at BITONIC_BLOCK_SIZE * 2 and going to the maximum number of particles);
    rem     This is achieved by transposing the data matrix, sorting its columns (which will be the rows after the transpose), then transposing it again
    rem     so that the initial rows are once again the rows in the matrix viewed by the shader program and sorting that.
    level = BITONIC_BLOCK_SIZE * 2
    while level <= MAX_NUM_PARTICLES
        rem Update constant buffer for transposing and sorting column data
        dx11 set cbuffer dword cbSortData, 0,  level / BITONIC_BLOCK_SIZE
        dx11 set cbuffer dword cbSortData, 4,  (level && (not MAX_NUM_PARTICLES)) / BITONIC_BLOCK_SIZE
        dx11 set cbuffer dword cbSortData, 8,  MATRIX_WIDTH
        dx11 set cbuffer dword cbSortData, 12, MATRIX_HEIGHT
        
        rem Transpose the (entire) data from index lookup buffer 1 into index lookup buffer 2
        dx11 set compute shader buffer   csMatrixTranspose, 0, bufIndexLookup
        dx11 set compute shader rwbuffer csMatrixTranspose, 0, bufIndexLookup2
        dx11 dispatch compute shader csMatrixTranspose, MATRIX_WIDTH / TRANSPOSE_BLOCK_SIZE, MATRIX_HEIGHT / TRANSPOSE_BLOCK_SIZE, 1
        
        rem Sort the transposed column data (that is now in lookup buffer 2)
        dx11 set compute shader rwbuffer csBitonicSort, 0, bufIndexLookup2
        dx11 dispatch compute shader csBitonicSort, MAX_NUM_PARTICLES / BITONIC_BLOCK_SIZE, 1, 1
        
        rem Update constant buffer for transposing and sorting row data
        dx11 set cbuffer dword cbSortData, 0,  BITONIC_BLOCK_SIZE
        dx11 set cbuffer dword cbSortData, 4,  level
        dx11 set cbuffer dword cbSortData, 8,  MATRIX_HEIGHT
        dx11 set cbuffer dword cbSortData, 12, MATRIX_WIDTH
        
        rem Transpose the data from lookup buffer 2 back into buffer 1 again
        dx11 set compute shader buffer   csMatrixTranspose, 0, bufIndexLookup2
        dx11 set compute shader rwbuffer csMatrixTranspose, 0, bufIndexLookup
        dx11 dispatch compute shader csMatrixTranspose, MATRIX_HEIGHT / TRANSPOSE_BLOCK_SIZE, MATRIX_WIDTH / TRANSPOSE_BLOCK_SIZE, 1
        
        rem Sort the transposed row data (in the first buffer that will act as input in the next iteration / contain the final sorted buffer once done)
        dx11 set compute shader rwbuffer csBitonicSort, 0, bufIndexLookup
        dx11 dispatch compute shader csBitonicSort, MAX_NUM_PARTICLES / BITONIC_BLOCK_SIZE, 1, 1
        
        rem Increment level to the next power of two
        level = level * 2
    endwhile
endfunction

