Thursday, December 31, 2009

Pixel Shader support (more like re-inventing the wheel)...

Today, I sat back and thought to myself, "Hmmm, maybe I should start fixing some internal issues with Cxbx rather than updating the HLE database for a while." I thought back to myself how no matter how hard I tried, I never could get that stupid BumpEarth demo in the XDK to work right! The base texture always rendered fine, but the bump map and the environment map never did work. I decided to investigate. Initially, I assumed that it was a missing/wrong deferred texture state. After browsing the source code for a while, I realized all that stuff was fine. I even compared the code to the PC version. I noticed that the Xbox version of BumpEarth used a pixel shader, but the PC version didn't. Since I never really bothered with Xbox's pixel shader stuff (or even checked Cxbx's) I didn't initially assume that was a problem. I thought that this would be a good opportunity to check out how Xbox's pixel shaders worked. Turns out that Xbox pixel shaders are converted to a structure. Sounds weird to me, but I'm sure Microsoft had a good reason. After finding out that this was so, I checked Cxbx's implementation of IDirect3DDevice8_CreatePixelShader. Turns out that there was no [dynamic] pixel shader support at all! Whoever wrote it just used a fall back pixel shader and that was the end of it. That's better than nothing, but it's time change that (because if I don't do it, it might not get done)! Kingofc did it before, but we have no idea where he's been off to...

"Oh, I'm sure it can't be that bad... can it?" Well, so far, the Xbox's documentation on how it's pixel shaders work are a bit vague. The XDK also supports "Hard Coded" pixel shaders via the D3DPIXELSHADERDEF structure. Here's the definition of it (the X_ prefix was added for Cxbx to prevent collisions):

typedef struct _X_D3DPIXELSHADERDEF
{
DWORD PSAlphaInputs[8]; // Alpha inputs for each stage
DWORD PSFinalCombinerInputsABCD; // Final combiner inputs
DWORD PSFinalCombinerInputsEFG; // Final combiner inputs (continued)
DWORD PSConstant0[8]; // C0 for each stage
DWORD PSConstant1[8]; // C1 for each stage
DWORD PSAlphaOutputs[8]; // Alpha output for each stage
DWORD PSRGBInputs[8]; // RGB inputs for each stage
DWORD PSCompareMode; // Compare modes for clipplane texture mode
DWORD PSFinalCombinerConstant0; // C0 in final combiner
DWORD PSFinalCombinerConstant1; // C1 in final combiner
DWORD PSRGBOutputs[8]; // Stage 0 RGB outputs
DWORD PSCombinerCount; // Active combiner count (Stages 0-7)
DWORD PSTextureModes; // Texture addressing modes
DWORD PSDotMapping; // Input mapping for dot product modes
DWORD PSInputTexture; // Texture source for some texture modes
DWORD PSC0Mapping; // Mapping of c0 regs to D3D constants
DWORD PSC1Mapping; // Mapping of c1 regs to D3D constants
DWORD PSFinalCombinerConstants; // Final combiner constant mapping
}X_D3DPIXELSHADERDEF;


At first glance, the use of each field looks a bit obvious, but transforming this into a pixel shader isn't a trivial task (at least not for me). So far, it's just now starting to make sense (I had to look in the header file instead of the XDK docs). In the end, all pixel shaders on Xbox take the form of the structure you see above.

For those who have been following Xbox emulation history as long as I have, you might recall the day that _SF_ (author of Xeon) released the source code to his Xbox -> PC Direct3D pixel shader conversion code in an effort to prove that his emulator was not fake. I took a look to see how _SF_ did it in Xeon. Turns out that instead of using assembly, he used HLSL instead. It was a brilliant idea, but it's not going to work for Cxbx since it still uses DirectX 8.1 (another reason why I really want to start using OpenGL soon). Assembly shaders will still be possible, but just a bit more tedious.

What have I done so far? I've already written some prelimary code for pixel shader generation. For now, the best thing to do is to test it against pixel shaders written by myself and XDK demos to ensure accuracy. So far, I've written 3 pixel shader examples. Nothing fancy. I've also written some code to output the contents of the structure. They vary from shader to shader. Here's the results.

shader 1:
xps.1.0 // Xbox PixelShader

mov r0, v0 // Move the diffuse vertex colour to the
// output colour register (r0).
output:
PSAphaInputs[8] = 0xD4301010 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
PSFinalCombinerInputsABCD = 0x00000000
PSFinalCombinerInputsEFG = 0x00000000
PSConstant0[8] = 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
PSConstant1[8] = 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
PSAlphaOutputs[8] = 0x000000C0 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
PSRGBInputs[8] = 0xC4200000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
PSCompareMode = 0x00000000
PSFinalCombinerConstant0 = 0x00000000
PSFinalCombinerConstant1 = 0x00000000
PSRGBOutputs[8] = 0x000000C0 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
PSCombinerCount = 0x00011101
PSTextureModes = 0x00000000
PSDotMapping = 0x00000000
PSInputTexture = 0x00000000
PSC0Mapping = 0xFFFFFFFF
PSC1Mapping = 0xFFFFFFFF
PSFinalCombinerConstants = 0x000001FF

shader 2:
xps.1.0 // Xbox PixelShader

tex t0 // Declare texture register t0 (Texture Stage 0)
mul r0, v0, t0 // Multiply v0 and t0 the output register (r0)

output:
PSAphaInputs[8] = 0xD4D81010 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
PSFinalCombinerInputsABCD = 0x00000000
PSFinalCombinerInputsEFG = 0x00000000
PSConstant0[8] = 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
PSConstant1[8] = 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
PSAlphaOutputs[8] = 0x000000C0 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
PSRGBInputs[8] = 0xC4C80000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
PSCompareMode = 0x00000000
PSFinalCombinerConstant0 = 0x00000000
PSFinalCombinerConstant1 = 0x00000000
PSRGBOutputs[8] = 0x000000C0 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
PSCombinerCount = 0x00011101
PSTextureModes = 0x00000001
PSDotMapping = 0x00000000
PSInputTexture = 0x00000000
PSC0Mapping = 0xFFFFFFFF
PSC1Mapping = 0xFFFFFFFF
PSFinalCombinerConstants = 0x000001FF

shader 3:
xps.1.0 // Xbox PixelShader

tex t0 // Declare texture register t0 (Texture Stage 0)
tex t1 // Declare texture register t0 (Texture Stage 1)
mov r1, t1 // Move texture register t1 into output register r1
lrp r0, v0, t0, r1 // Lerp between t0 and r1 by proportion
// specified in v0
output:
PSAphaInputs[8] = 0xD9301010 0x14D8DD34 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
PSFinalCombinerInputsABCD = 0x00000000
PSFinalCombinerInputsEFG = 0x00000000
PSConstant0[8] = 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
PSConstant1[8] = 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
PSAlphaOutputs[8] = 0x000000D0 0x00000C00 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
PSRGBInputs[8] = 0xC9200000 0x04C8CD24 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
PSCompareMode = 0x00000000
PSFinalCombinerConstant0 = 0x00000000
PSFinalCombinerConstant1 = 0x00000000
PSRGBOutputs[8] = 0x000000D0 0x00000C00 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000 0x00000000
PSCombinerCount = 0x00011102
PSTextureModes = 0x00000021
PSDotMapping = 0x00000000
PSInputTexture = 0x00000000
PSC0Mapping = 0xFFFFFFFF
PSC1Mapping = 0xFFFFFFFF
PSFinalCombinerConstants = 0x000001FF


So obviously enough, the contents of the structure will vary on the contents of your shader. Quite frankly, it reminds me of .fx files. They are very convienent to use, but once again, not available in D3D8.

Now that I think about it, there is one more option for this... Cg! IMO Cg is awesome shader language (I don't understand why so many hate it) and it supports Direct3D 8.1 too! It's not very different from HLSL either. Although it should work fine on ATI cards, it will make Cxbx much more NVIDIA oriented than it already is.

So, that's what I've been working on lately. I really hope that I can get this pixel shader stuff working sometime. Turok is looking a bit "lifeless" without it's lighting shaders emulated properly. Lack of pixel shaders also limit Cxbx's bumpmapping support too. I'll be sure to keep you all posted on what's going on with this latest endeavour.

Shogun.

EDIT: Sorry, blogger is not very code friendly.

4 comments:

  1. Wow! You have been busy, Shogun! I really enjoy seeing the progress made on this emulator, every step of the way.

    "Although it should work fine on ATI cards, it will make Cxbx much more NVIDIA oriented than it already is." Hahaha. Looks like the gtx 275 I am ordering will come in handy after all.

    -Mr. Fabulous

    ReplyDelete
  2. Not to mention that pixel shader support will be a very significant step once its finally implemented.

    -Mr. Fabulous

    ReplyDelete