Sunday, November 22, 2009

Unified Codebases Rock

In a previous post, I eluded to the advantages of being able to run the same code on the CPU and the GPU. One obvious example is the interaction between color processing and frame buffer clearing. Shaders may process the color of a fragment before it is written to the frame buffer for a multitude of reasons, including color correction, exposure control, tone mapping, and gamma correction. This causes problems when the CPU needs to predict the final value for a color that will be output by a shader. For example, when fog is used to attenuate the colors of objects as they fade into the distance, it is necessary to match the clear color with the fog color. If the same color processing is not applied to both, the result is the following:

On the other hand, if the correct color processing is applied to the clear color, the result is much more satisfying:

The problem with the current shader paradigm is that shader code has entirely different semantics than CPU code and is kept in a separate code base. Executing shader functions on the CPU side is at best difficult, and is typically entirely unsupported. The traditional workaround to this issue is to rewrite the necessary functions in the language being used on the CPU. This results in code duplication, however, and complicates maintaining and evolving the rendering system.

PyStream avoids this problem by using the same language on the CPU and the GPU, and unifying the codebases. With a few reasonable restrictions, most functions can be executed on either the CPU or the GPU.

The fragment shader is that generated the images in this post was as follows:

def shadeFragment(self, context, pos, normal, texCoord):
    surface = self.material.surface(pos, normal.normalize())

    # Texture
    surface.diffuseColor *= self.sampler.texture(texCoord).xyz
    # Accumulate lighting
    self.ambient.accumulate(surface, self.worldToCamera)
    self.light.accumulate(surface, self.worldToCamera)
    mainColor = surface.litColor()
    mainColor = self.fog.apply(mainColor, surface.p)
    mainColor = self.processOutputColor(mainColor)
    mainColor = vec4(mainColor, 1.0)
    context.colors = (mainColor,)

def processOutputColor(self, color):
    return rgb2srgb(tonemap(color))

The clear color is generated by calling the shader's processOutputColor method:

clearColor = shader.processOutputColor(shader.fog.color)

No comments:

Post a Comment