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:

  • readonly in GLSL maps to the NonWritable decoration in SPIR-V.
  • writeonly in GLSL maps to the NonReadable decoration 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 the shaderStorageImageReadWithoutFormat feature). 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 NonWritable resource corresponds strictly to read-access masks, such as VK_ACCESS_SHADER_READ_BIT.
  • A NonReadable resource corresponds strictly to write-access masks, such as VK_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.