Skip to main content
TLDR emphasize added
Source Link
Kromster
  • 10.7k
  • 4
  • 54
  • 67

This is not a WebGL 2 restriction. This is fundamentally how GPUs and their 3D rendering APIs all work.

Because they're designed to render many triangles and fragments in parallel, they structurally eliminate data dependencies between these operations. A particular chunk of memory is either read-only (texture) or write-only (frame buffer / render target) for the full rendering pass.

If you want to change to read some memory, all pending writes to it need to finish first (a full pipeline flush). This would be unimaginably slow if it had to happen for every pixel — your super fast parallel processor would effectively become serial. So the APIs are designed so you can't write code that could incur such a serial penalty.

(There are some exceptions involving compute shaders, unordered access views, etc., but they're more specialized, and generally not a performant way to do a big bulk operation like a full-screen post effect where you know you want to write one value for every screen pixel)

Every post-processing system you've ever seen — yes, even that one that ran at ridiculously high frame rates — works using the "ping pong" approach you describe. You render the scene to one buffer, then read that buffer to apply post effects and write them to a second buffer, which is ultimately shown to the screen.You render the scene to one buffer, then read that buffer to apply post effects and write them to a second buffer, which is ultimately shown to the screen.

Your assumption that this would be unreasonably slow due to "copying" is not quite right. Whether writing to a new buffer or overwriting the value in-place, we still have to do the same work:

  1. Sampling a buffer

  2. Calculating a new colour based on the value read

  3. Writing that colour to a buffer

The only difference is whether the write destination is the same as the read destination. These are cached separately on GPUs so you don't incur a performance penalty from writing to a different spot of memory — in fact you gain performance because there's no need to interleave loads and stores from the same memory chunk.

In fact, this can even give you more control over performance by allowing you to render your scene at a reduced resolution when needed, then apply a post-process resolve to map it to a full-res buffer to present to the screen, in a more sophisticated way than a bilinear upscale. "Dynamic resolution", checkerboard rendering, DLSS, and other strategies for getting high framerates at high resolutions work in roughly this way.

Modern games will often have many of these off-screen buffers, used for things like deferred rendering G-buffers, previous frames' output for motion blur, screenspace reflection, or temporal antialiasing, downsampling the frame buffer for fast bloom blurs, and all sorts of other effects.

It's not free, but generally fill rate (the number of times you write a value out — whether to the same place or a new one) is more likely to be a bottleneck on modern GPUs than the amount of memory used for render targets.

This is not a WebGL 2 restriction. This is fundamentally how GPUs and their 3D rendering APIs all work.

Because they're designed to render many triangles and fragments in parallel, they structurally eliminate data dependencies between these operations. A particular chunk of memory is either read-only (texture) or write-only (frame buffer / render target) for the full rendering pass.

If you want to change to read some memory, all pending writes to it need to finish first (a full pipeline flush). This would be unimaginably slow if it had to happen for every pixel — your super fast parallel processor would effectively become serial. So the APIs are designed so you can't write code that could incur such a serial penalty.

(There are some exceptions involving compute shaders, unordered access views, etc., but they're more specialized, and generally not a performant way to do a big bulk operation like a full-screen post effect where you know you want to write one value for every screen pixel)

Every post-processing system you've ever seen — yes, even that one that ran at ridiculously high frame rates — works using the "ping pong" approach you describe. You render the scene to one buffer, then read that buffer to apply post effects and write them to a second buffer, which is ultimately shown to the screen.

Your assumption that this would be unreasonably slow due to "copying" is not quite right. Whether writing to a new buffer or overwriting the value in-place, we still have to do the same work:

  1. Sampling a buffer

  2. Calculating a new colour based on the value read

  3. Writing that colour to a buffer

The only difference is whether the write destination is the same as the read destination. These are cached separately on GPUs so you don't incur a performance penalty from writing to a different spot of memory — in fact you gain performance because there's no need to interleave loads and stores from the same memory chunk.

In fact, this can even give you more control over performance by allowing you to render your scene at a reduced resolution when needed, then apply a post-process resolve to map it to a full-res buffer to present to the screen, in a more sophisticated way than a bilinear upscale. "Dynamic resolution", checkerboard rendering, DLSS, and other strategies for getting high framerates at high resolutions work in roughly this way.

Modern games will often have many of these off-screen buffers, used for things like deferred rendering G-buffers, previous frames' output for motion blur, screenspace reflection, or temporal antialiasing, downsampling the frame buffer for fast bloom blurs, and all sorts of other effects.

It's not free, but generally fill rate (the number of times you write a value out — whether to the same place or a new one) is more likely to be a bottleneck on modern GPUs than the amount of memory used for render targets.

This is not a WebGL 2 restriction. This is fundamentally how GPUs and their 3D rendering APIs all work.

Because they're designed to render many triangles and fragments in parallel, they structurally eliminate data dependencies between these operations. A particular chunk of memory is either read-only (texture) or write-only (frame buffer / render target) for the full rendering pass.

If you want to change to read some memory, all pending writes to it need to finish first (a full pipeline flush). This would be unimaginably slow if it had to happen for every pixel — your super fast parallel processor would effectively become serial. So the APIs are designed so you can't write code that could incur such a serial penalty.

(There are some exceptions involving compute shaders, unordered access views, etc., but they're more specialized, and generally not a performant way to do a big bulk operation like a full-screen post effect where you know you want to write one value for every screen pixel)

Every post-processing system you've ever seen — yes, even that one that ran at ridiculously high frame rates — works using the "ping pong" approach you describe. You render the scene to one buffer, then read that buffer to apply post effects and write them to a second buffer, which is ultimately shown to the screen.

Your assumption that this would be unreasonably slow due to "copying" is not quite right. Whether writing to a new buffer or overwriting the value in-place, we still have to do the same work:

  1. Sampling a buffer

  2. Calculating a new colour based on the value read

  3. Writing that colour to a buffer

The only difference is whether the write destination is the same as the read destination. These are cached separately on GPUs so you don't incur a performance penalty from writing to a different spot of memory — in fact you gain performance because there's no need to interleave loads and stores from the same memory chunk.

In fact, this can even give you more control over performance by allowing you to render your scene at a reduced resolution when needed, then apply a post-process resolve to map it to a full-res buffer to present to the screen, in a more sophisticated way than a bilinear upscale. "Dynamic resolution", checkerboard rendering, DLSS, and other strategies for getting high framerates at high resolutions work in roughly this way.

Modern games will often have many of these off-screen buffers, used for things like deferred rendering G-buffers, previous frames' output for motion blur, screenspace reflection, or temporal antialiasing, downsampling the frame buffer for fast bloom blurs, and all sorts of other effects.

It's not free, but generally fill rate (the number of times you write a value out — whether to the same place or a new one) is more likely to be a bottleneck on modern GPUs than the amount of memory used for render targets.

adding render target
Source Link
DMGregory
  • 136.3k
  • 22
  • 247
  • 373

This is not a WebGL 2 restriction. This is fundamentally how GPUs and their 3D rendering APIs all work.

Because they're designed to render many triangles and fragments in parallel, they structurally eliminate data dependencies between these operations. A particular chunk of memory is either read-only (texture) or write-only (frame buffer / render target) for the full rendering pass.

If you want to change to read some memory, all pending writes to it need to finish first (a full pipeline flush). This would be unimaginably slow if it had to happen for every pixel — your super fast parallel processor would effectively become serial. So the APIs are designed so you can't write code that could incur such a serial penalty.

(There are some exceptions involving compute shaders, unordered access views, etc., but they're more specialized, and generally not a performant way to do a big bulk operation like a full-screen post effect where you know you want to write one value for every screen pixel)

Every post-processing system you've ever seen — yes, even that one that ran at ridiculously high frame rates — works using the "ping pong" approach you describe. You render the scene to one buffer, then read that buffer to apply post effects and write them to a second buffer, which is ultimately shown to the screen.

Your assumption that this would be unreasonably slow due to "copying" is not quite right. Whether writing to a new buffer or overwriting the value in-place, we still have to do the same work:

  1. Sampling a buffer

  2. Calculating a new colour based on the value read

  3. Writing that colour to a buffer

The only difference is whether the write destination is the same as the read destination. These are cached separately on GPUs so you don't incur a performance penalty from writing to a different spot of memory — in fact you gain performance because there's no need to interleave loads and stores from the same memory chunk.

In fact, this can even give you more control over performance by allowing you to render your scene at a reduced resolution when needed, then apply a post-process resolve to map it to a full-res buffer to present to the screen, in a more sophisticated way than a bilinear upscale. "Dynamic resolution", checkerboard rendering, DLSS, and other strategies for getting high framerates at high resolutions work in roughly this way.

Modern games will often have many of these off-screen buffers, used for things like deferred rendering G-buffers, previous frames' output for motion blur, screenspace reflection, or temporal antialiasing, downsampling the frame buffer for fast bloom blurs, and all sorts of other effects.

It's not free, but generally fill rate (the number of times you write a value out — whether to the same place or a new one) is more likely to be a bottleneck on modern GPUs than the amount of memory used for render targets.

This is not a WebGL 2 restriction. This is fundamentally how GPUs and their 3D rendering APIs all work.

Because they're designed to render many triangles and fragments in parallel, they structurally eliminate data dependencies between these operations. A particular chunk of memory is either read-only (texture) or write-only (frame buffer) for the full rendering pass.

If you want to change to read some memory, all pending writes to it need to finish first (a full pipeline flush). This would be unimaginably slow if it had to happen for every pixel — your super fast parallel processor would effectively become serial. So the APIs are designed so you can't write code that could incur such a serial penalty.

(There are some exceptions involving compute shaders, unordered access views, etc., but they're more specialized, and generally not a performant way to do a big bulk operation like a full-screen post effect where you know you want to write one value for every screen pixel)

Every post-processing system you've ever seen — yes, even that one that ran at ridiculously high frame rates — works using the "ping pong" approach you describe. You render the scene to one buffer, then read that buffer to apply post effects and write them to a second buffer, which is ultimately shown to the screen.

Your assumption that this would be unreasonably slow due to "copying" is not quite right. Whether writing to a new buffer or overwriting the value in-place, we still have to do the same work:

  1. Sampling a buffer

  2. Calculating a new colour based on the value read

  3. Writing that colour to a buffer

The only difference is whether the write destination is the same as the read destination. These are cached separately on GPUs so you don't incur a performance penalty from writing to a different spot of memory — in fact you gain performance because there's no need to interleave loads and stores from the same memory chunk.

In fact, this can even give you more control over performance by allowing you to render your scene at a reduced resolution when needed, then apply a post-process resolve to map it to a full-res buffer to present to the screen, in a more sophisticated way than a bilinear upscale. "Dynamic resolution", checkerboard rendering, DLSS, and other strategies for getting high framerates at high resolutions work in roughly this way.

Modern games will often have many of these off-screen buffers, used for things like deferred rendering G-buffers, previous frames' output for motion blur, screenspace reflection, or temporal antialiasing, downsampling the frame buffer for fast bloom blurs, and all sorts of other effects.

It's not free, but generally fill rate (the number of times you write a value out — whether to the same place or a new one) is more likely to be a bottleneck on modern GPUs than the amount of memory used for render targets.

This is not a WebGL 2 restriction. This is fundamentally how GPUs and their 3D rendering APIs all work.

Because they're designed to render many triangles and fragments in parallel, they structurally eliminate data dependencies between these operations. A particular chunk of memory is either read-only (texture) or write-only (frame buffer / render target) for the full rendering pass.

If you want to change to read some memory, all pending writes to it need to finish first (a full pipeline flush). This would be unimaginably slow if it had to happen for every pixel — your super fast parallel processor would effectively become serial. So the APIs are designed so you can't write code that could incur such a serial penalty.

(There are some exceptions involving compute shaders, unordered access views, etc., but they're more specialized, and generally not a performant way to do a big bulk operation like a full-screen post effect where you know you want to write one value for every screen pixel)

Every post-processing system you've ever seen — yes, even that one that ran at ridiculously high frame rates — works using the "ping pong" approach you describe. You render the scene to one buffer, then read that buffer to apply post effects and write them to a second buffer, which is ultimately shown to the screen.

Your assumption that this would be unreasonably slow due to "copying" is not quite right. Whether writing to a new buffer or overwriting the value in-place, we still have to do the same work:

  1. Sampling a buffer

  2. Calculating a new colour based on the value read

  3. Writing that colour to a buffer

The only difference is whether the write destination is the same as the read destination. These are cached separately on GPUs so you don't incur a performance penalty from writing to a different spot of memory — in fact you gain performance because there's no need to interleave loads and stores from the same memory chunk.

In fact, this can even give you more control over performance by allowing you to render your scene at a reduced resolution when needed, then apply a post-process resolve to map it to a full-res buffer to present to the screen, in a more sophisticated way than a bilinear upscale. "Dynamic resolution", checkerboard rendering, DLSS, and other strategies for getting high framerates at high resolutions work in roughly this way.

Modern games will often have many of these off-screen buffers, used for things like deferred rendering G-buffers, previous frames' output for motion blur, screenspace reflection, or temporal antialiasing, downsampling the frame buffer for fast bloom blurs, and all sorts of other effects.

It's not free, but generally fill rate (the number of times you write a value out — whether to the same place or a new one) is more likely to be a bottleneck on modern GPUs than the amount of memory used for render targets.

Adding caveat
Source Link
DMGregory
  • 136.3k
  • 22
  • 247
  • 373

This is not a WebGL 2 restriction. This is fundamentally how GPUs and their 3D rendering APIs all work.

Because they're designed to render many triangles and fragments in parallel, they structurally eliminate data dependencies between these operations. A particular chunk of memory is either read-only (texture) or write-only (frame buffer) for the full rendering pass.

If you want to change to read some memory, all pending writes to it need to finish first (a full pipeline flush). This would be unimaginably slow if it had to happen for every pixel — your super fast parallel processor would effectively become serial. So the APIs are designed so you can't write code that could incur such a serial penalty.

(There are some exceptions involving compute shaders, unordered access views, etc., but they're more specialized, and generally not a performant way to do a big bulk operation like a full-screen post effect where you know you want to write one value for every screen pixel)

Every post-processing system you've ever seen — yes, even that one that ran at ridiculously high frame rates — works using the "ping pong" approach you describe. You render the scene to one buffer, then read that buffer to apply post effects and write them to a second buffer, which is ultimately shown to the screen.

Your assumption that this would be unreasonably slow due to "copying" is not quite right. Whether writing to a new buffer or overwriting the value in-place, we still have to do the same work:

  1. Sampling a buffer

  2. Calculating a new colour based on the value read

  3. Writing that colour to a buffer

The only difference is whether the write destination is the same as the read destination. These are cached separately on GPUs so you don't incur a performance penalty from writing to a different spot of memory — in fact you gain performance because there's no need to interleave loads and stores from the same memory chunk.

In fact, this can even give you more control over performance by allowing you to render your scene at a reduced resolution when needed, then apply a post-process resolve to map it to a full-res buffer to present to the screen, in a more sophisticated way than a bilinear upscale. "Dynamic resolution", checkerboard rendering, DLSS, and other strategies for getting high framerates at high resolutions work in roughly this way.

Modern games will often have many of these off-screen buffers, used for things like deferred rendering G-buffers, previous frames' output for motion blur, screenspace reflection, or temporal antialiasing, downsampling the frame buffer for fast bloom blurs, and all sorts of other effects.

It's not free, but generally fill rate (the number of times you write a value out — whether to the same place or a new one) is more likely to be a bottleneck on modern GPUs than the amount of memory used for render targets.

This is not a WebGL 2 restriction. This is fundamentally how GPUs and their 3D rendering APIs all work.

Because they're designed to render many triangles and fragments in parallel, they structurally eliminate data dependencies between these operations. A particular chunk of memory is either read-only (texture) or write-only (frame buffer) for the full rendering pass.

If you want to change to read some memory, all pending writes to it need to finish first (a full pipeline flush). This would be unimaginably slow if it had to happen for every pixel — your super fast parallel processor would effectively become serial. So the APIs are designed so you can't write code that could incur such a serial penalty.

Every post-processing system you've ever seen — yes, even that one that ran at ridiculously high frame rates — works using the "ping pong" approach you describe. You render the scene to one buffer, then read that buffer to apply post effects and write them to a second buffer, which is ultimately shown to the screen.

Your assumption that this would be unreasonably slow due to "copying" is not quite right. Whether writing to a new buffer or overwriting the value in-place, we still have to do the same work:

  1. Sampling a buffer

  2. Calculating a new colour based on the value read

  3. Writing that colour to a buffer

The only difference is whether the write destination is the same as the read destination. These are cached separately on GPUs so you don't incur a performance penalty from writing to a different spot of memory — in fact you gain performance because there's no need to interleave loads and stores from the same memory chunk.

In fact, this can even give you more control over performance by allowing you to render your scene at a reduced resolution when needed, then apply a post-process resolve to map it to a full-res buffer to present to the screen, in a more sophisticated way than a bilinear upscale. "Dynamic resolution", checkerboard rendering, DLSS, and other strategies for getting high framerates at high resolutions work in roughly this way.

Modern games will often have many of these off-screen buffers, used for things like deferred rendering G-buffers, previous frames' output for motion blur, screenspace reflection, or temporal antialiasing, downsampling the frame buffer for fast bloom blurs, and all sorts of other effects.

It's not free, but generally fill rate (the number of times you write a value out — whether to the same place or a new one) is more likely to be a bottleneck on modern GPUs than the amount of memory used for render targets.

This is not a WebGL 2 restriction. This is fundamentally how GPUs and their 3D rendering APIs all work.

Because they're designed to render many triangles and fragments in parallel, they structurally eliminate data dependencies between these operations. A particular chunk of memory is either read-only (texture) or write-only (frame buffer) for the full rendering pass.

If you want to change to read some memory, all pending writes to it need to finish first (a full pipeline flush). This would be unimaginably slow if it had to happen for every pixel — your super fast parallel processor would effectively become serial. So the APIs are designed so you can't write code that could incur such a serial penalty.

(There are some exceptions involving compute shaders, unordered access views, etc., but they're more specialized, and generally not a performant way to do a big bulk operation like a full-screen post effect where you know you want to write one value for every screen pixel)

Every post-processing system you've ever seen — yes, even that one that ran at ridiculously high frame rates — works using the "ping pong" approach you describe. You render the scene to one buffer, then read that buffer to apply post effects and write them to a second buffer, which is ultimately shown to the screen.

Your assumption that this would be unreasonably slow due to "copying" is not quite right. Whether writing to a new buffer or overwriting the value in-place, we still have to do the same work:

  1. Sampling a buffer

  2. Calculating a new colour based on the value read

  3. Writing that colour to a buffer

The only difference is whether the write destination is the same as the read destination. These are cached separately on GPUs so you don't incur a performance penalty from writing to a different spot of memory — in fact you gain performance because there's no need to interleave loads and stores from the same memory chunk.

In fact, this can even give you more control over performance by allowing you to render your scene at a reduced resolution when needed, then apply a post-process resolve to map it to a full-res buffer to present to the screen, in a more sophisticated way than a bilinear upscale. "Dynamic resolution", checkerboard rendering, DLSS, and other strategies for getting high framerates at high resolutions work in roughly this way.

Modern games will often have many of these off-screen buffers, used for things like deferred rendering G-buffers, previous frames' output for motion blur, screenspace reflection, or temporal antialiasing, downsampling the frame buffer for fast bloom blurs, and all sorts of other effects.

It's not free, but generally fill rate (the number of times you write a value out — whether to the same place or a new one) is more likely to be a bottleneck on modern GPUs than the amount of memory used for render targets.

Elaborating
Source Link
DMGregory
  • 136.3k
  • 22
  • 247
  • 373
Loading
Source Link
DMGregory
  • 136.3k
  • 22
  • 247
  • 373
Loading