Vulkan GLSL NonWritable and NonReadable Qualifiers
In Vulkan shader development, descriptor memory qualifiers define how
resources like storage buffers and storage images are accessed during
execution. In GLSL, these are specified using the readonly
and writeonly keywords, which the compiler translates to
the SPIR-V decorations NonWritable and
NonReadable, respectively. This article breaks down the
technical roles of these qualifiers, how they guide compiler and
hardware optimizations, and their impact on GPU memory access
patterns.
Mapping GLSL Keywords to SPIR-V Decorations
While developers write shaders in GLSL using high-level type qualifiers, the Vulkan driver interacts with SPIR-V bytecode. The translation works as follows:
readonlyin GLSL maps to theNonWritabledecoration in SPIR-V.writeonlyin GLSL maps to theNonReadabledecoration in SPIR-V.
These decorations apply directly to descriptor variable declarations,
specifically storage buffers (buffer blocks) and storage
images (image2D, image3D, etc.).
The Role of NonWritable
(readonly)
The NonWritable qualifier explicitly states that a
shader invocation will only read data from the associated resource and
will never modify it.
Compile-Time Enforcement
Applying readonly prevents accidental writes within the
shader code. Any attempt to write or assign values to a resource marked
readonly produces a compilation error.
Hardware and Driver Optimizations
Marking memory as NonWritable allows both the shader
compiler and the underlying GPU architecture to apply specific
performance optimizations:
- Cache Allocation: The GPU can route memory requests through dedicated read-only texture or constant caches instead of generic read-write caches, significantly improving memory throughput and hit rates.
- Instruction Scheduling: The compiler knows the resource's state will not mutate during shader execution. It can safely vectorize read operations, hoist memory fetches out of loops, and schedule memory loads early without worrying about read-after-write (RAW) hazards.
- Pipeline Coherency: Since no writes occur, the hardware does not need to manage cache flushes or dirty lines for that resource at the end of the invocation.
The Role of NonReadable
(writeonly)
The NonReadable qualifier declares that a shader
invocation will only write data to the target resource and will never
read existing values.
Compile-Time Enforcement
Marking an image or buffer variable as writeonly ensures
that instructions like imageLoad() or memory read
operations on that resource are prohibited, producing a compilation
error if attempted.
Hardware and Driver Optimizations
Writing data without reading provides substantial architectural advantages:
- Bypassing Read Operations: Because the shader never needs current data, the hardware can utilize streaming write mechanisms or write-combining buffers. This prevents unnecessary memory reads into the cache line before updating it (avoiding read-for-ownership overhead).
- Resource Format Flexibility (Storage Images): In
Vulkan GLSL, storage images used without an explicit image format layout
qualifier must be declared as
writeonly(unless the device supports theshaderStorageImageReadWithoutFormatfeature). This enables generic store instructions without requiring the pipeline to handle format conversion during loads.
Synchronization and Vulkan Pipeline Alignment
Using NonWritable and NonReadable ensures
proper architectural alignment with Vulkan pipeline barriers:
- A
NonWritableresource corresponds strictly to read-access masks, such asVK_ACCESS_SHADER_READ_BIT. - A
NonReadableresource corresponds strictly to write-access masks, such asVK_ACCESS_SHADER_WRITE_BIT.
If a resource is left unqualified (acting as read-write), the
pipeline must account for read-after-write and write-after-read hazards
inside the shader, requiring stricter memory barriers, coherency
controls (coherent), and conservative caching paths that
can degrade performance.
Applying NonWritable and NonReadable
decorations clarifies shader intent, prevents programmer error, and
grants the GPU the architectural freedom to route transactions through
the fastest available memory paths.