2020-12-12 04:30:20 +11:00
|
|
|
// SPDX-License-Identifier: Apache-2.0 OR MIT OR Unlicense
|
|
|
|
|
|
|
|
layout(set = 0, binding = 0) buffer Memory {
|
|
|
|
// offset into memory of the next allocation, initialized by the user.
|
|
|
|
uint mem_offset;
|
2022-06-24 01:48:26 +10:00
|
|
|
// mem_error is a bitmask of stages that have failed allocation.
|
2020-12-24 22:00:53 +11:00
|
|
|
uint mem_error;
|
2022-06-24 01:48:26 +10:00
|
|
|
// offset into blend memory of allocations for blend stack.
|
|
|
|
uint blend_offset;
|
2020-12-12 04:30:20 +11:00
|
|
|
uint[] memory;
|
|
|
|
};
|
|
|
|
|
2020-12-24 22:00:53 +11:00
|
|
|
// Uncomment this line to add the size field to Alloc and enable memory checks.
|
|
|
|
// Note that the Config struct in setup.h grows size fields as well.
|
|
|
|
|
2022-06-24 01:48:26 +10:00
|
|
|
// This setting is not working and the mechanism will be removed.
|
|
|
|
//#define MEM_DEBUG
|
2020-12-24 22:00:53 +11:00
|
|
|
|
2021-03-31 05:16:36 +11:00
|
|
|
#ifdef MEM_DEBUG
|
|
|
|
#define Alloc_size 16
|
|
|
|
#else
|
2022-06-24 01:48:26 +10:00
|
|
|
// TODO: this seems wrong
|
2020-12-24 22:00:53 +11:00
|
|
|
#define Alloc_size 8
|
2021-03-31 05:16:36 +11:00
|
|
|
#endif
|
2020-12-24 22:00:53 +11:00
|
|
|
|
2020-12-12 04:30:20 +11:00
|
|
|
// Alloc represents a memory allocation.
|
|
|
|
struct Alloc {
|
|
|
|
// offset in bytes into memory.
|
|
|
|
uint offset;
|
2020-12-24 22:00:53 +11:00
|
|
|
#ifdef MEM_DEBUG
|
|
|
|
// size in bytes of the allocation.
|
|
|
|
uint size;
|
|
|
|
#endif
|
|
|
|
};
|
|
|
|
|
2021-03-31 05:16:36 +11:00
|
|
|
// new_alloc synthesizes an Alloc from an offset and size.
|
2021-04-12 22:41:03 +10:00
|
|
|
Alloc new_alloc(uint offset, uint size, bool mem_ok) {
|
2020-12-12 04:30:20 +11:00
|
|
|
Alloc a;
|
2020-12-24 22:00:53 +11:00
|
|
|
a.offset = offset;
|
|
|
|
#ifdef MEM_DEBUG
|
2021-04-12 22:41:03 +10:00
|
|
|
if (mem_ok) {
|
|
|
|
a.size = size;
|
|
|
|
} else {
|
|
|
|
a.size = 0;
|
|
|
|
}
|
2020-12-24 22:00:53 +11:00
|
|
|
#endif
|
2020-12-12 04:30:20 +11:00
|
|
|
return a;
|
|
|
|
}
|
2020-12-24 22:00:53 +11:00
|
|
|
|
2022-06-24 01:48:26 +10:00
|
|
|
#define STAGE_BINNING (1u << 0)
|
|
|
|
#define STAGE_TILE_ALLOC (1u << 1)
|
|
|
|
#define STAGE_PATH_COARSE (1u << 2)
|
|
|
|
#define STAGE_COARSE (1u << 3)
|
|
|
|
|
|
|
|
// Allocations in main memory will never be 0, and this might be slightly
|
|
|
|
// faster to test against than some other value.
|
|
|
|
#define MALLOC_FAILED 0
|
|
|
|
|
|
|
|
// Check that previous dependent stages have succeeded.
|
|
|
|
bool check_deps(uint dep_stage) {
|
|
|
|
// TODO: this should be an atomic relaxed load, but that involves
|
|
|
|
// bringing in "memory scope semantics"
|
|
|
|
return (atomicOr(mem_error, 0) & dep_stage) == 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Allocate size bytes of memory, offset in bytes.
|
|
|
|
// Note: with a bit of rearrangement of header files, we could make the
|
|
|
|
// mem_size argument go away (it comes from the config binding).
|
|
|
|
uint malloc_stage(uint size, uint mem_size, uint stage) {
|
2020-12-24 22:00:53 +11:00
|
|
|
uint offset = atomicAdd(mem_offset, size);
|
2022-06-24 01:48:26 +10:00
|
|
|
if (offset + size > mem_size) {
|
|
|
|
atomicOr(mem_error, stage);
|
|
|
|
offset = MALLOC_FAILED;
|
2020-12-24 22:00:53 +11:00
|
|
|
}
|
2022-06-24 01:48:26 +10:00
|
|
|
return offset;
|
2020-12-24 22:00:53 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
// touch_mem checks whether access to the memory word at offset is valid.
|
|
|
|
// If MEM_DEBUG is defined, touch_mem returns false if offset is out of bounds.
|
|
|
|
// Offset is in words.
|
|
|
|
bool touch_mem(Alloc alloc, uint offset) {
|
|
|
|
#ifdef MEM_DEBUG
|
|
|
|
if (offset < alloc.offset/4 || offset >= (alloc.offset + alloc.size)/4) {
|
|
|
|
atomicMax(mem_error, ERR_OUT_OF_BOUNDS);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// write_mem writes val to memory at offset.
|
|
|
|
// Offset is in words.
|
|
|
|
void write_mem(Alloc alloc, uint offset, uint val) {
|
|
|
|
if (!touch_mem(alloc, offset)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
memory[offset] = val;
|
|
|
|
}
|
|
|
|
|
|
|
|
// read_mem reads the value from memory at offset.
|
|
|
|
// Offset is in words.
|
|
|
|
uint read_mem(Alloc alloc, uint offset) {
|
|
|
|
if (!touch_mem(alloc, offset)) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
uint v = memory[offset];
|
|
|
|
return v;
|
|
|
|
}
|
|
|
|
|
|
|
|
// slice_mem returns a sub-allocation inside another. Offset and size are in
|
|
|
|
// bytes, relative to a.offset.
|
|
|
|
Alloc slice_mem(Alloc a, uint offset, uint size) {
|
|
|
|
#ifdef MEM_DEBUG
|
|
|
|
if ((offset & 3) != 0 || (size & 3) != 0) {
|
|
|
|
atomicMax(mem_error, ERR_UNALIGNED_ACCESS);
|
|
|
|
return Alloc(0, 0);
|
|
|
|
}
|
|
|
|
if (offset + size > a.size) {
|
|
|
|
// slice_mem is sometimes used for slices outside bounds,
|
|
|
|
// but never written.
|
|
|
|
return Alloc(0, 0);
|
|
|
|
}
|
2021-04-12 22:41:03 +10:00
|
|
|
return Alloc(a.offset + offset, size);
|
|
|
|
#else
|
|
|
|
return Alloc(a.offset + offset);
|
2020-12-24 22:00:53 +11:00
|
|
|
#endif
|
|
|
|
}
|
2021-03-31 05:16:36 +11:00
|
|
|
|
|
|
|
// alloc_write writes alloc to memory at offset bytes.
|
|
|
|
void alloc_write(Alloc a, uint offset, Alloc alloc) {
|
|
|
|
write_mem(a, offset >> 2, alloc.offset);
|
|
|
|
#ifdef MEM_DEBUG
|
|
|
|
write_mem(a, (offset >> 2) + 1, alloc.size);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
// alloc_read reads an Alloc from memory at offset bytes.
|
|
|
|
Alloc alloc_read(Alloc a, uint offset) {
|
|
|
|
Alloc alloc;
|
|
|
|
alloc.offset = read_mem(a, offset >> 2);
|
|
|
|
#ifdef MEM_DEBUG
|
|
|
|
alloc.size = read_mem(a, (offset >> 2) + 1);
|
|
|
|
#endif
|
|
|
|
return alloc;
|
|
|
|
}
|