Sunday, February 28, 2010

Easy peasey

I'd hokied something up in Silverlight 2, which had me creating individual PNGs in memory and popping them into an image. It was, as you can image, quite painfully slow, but made a cute demo.

I don't know why it took so long to get around to it, but this took all of 15 minutes.. Just dummied up the sound/control/filesystem bindings for the moment. I'll do sound now, Pete Brown posted exactly what I need on his blog for his c64 emulator, so I'll go ahead and share and share alike. I hope my copy/paste keys work.

(Update: they work just fine.. Now with sound. I will not make this playable, in fact the rom image used has the main game engine disabled for now. This is a demonstration only, go pay 5 bucks on WiiWare if you want to play!)

But, if anyone is out there, tell me both how crappy/awesome your pc is and how fast it is running, both in and out of the browser (right click and install it).

Friday, February 26, 2010

More D3D fullscreen notes

Firstly, I was keeping both my full and windowed swapchains alive, and getting a SEHException trying to dispose, so firing up the DebugView told me:

[3308] DXGI Error:
[3308] Swapchain Released while fullscreen. Switch it to the windowed state first.


Fair enough. I decided to change the code to create a new form, new swap chain, and new render target for every switch to fullscreen, and to Dispose it when switched back. If the application tries to exit while fullscreen, the swapchain is switched to windowed and the form destroyed before this can happen. Seems fine to me so far.

But DebugView had more to say:

[3308] DXGI Warning:
[3308] IDXGISwapChain::Present: Fullscreen presentation inefficiencies incurred due to application not using IDXGISwapChain::ResizeBuffers appropriately, specifying a DXGI_MODE_DESC not available in IDXGIOutput::GetDisplayModeList, or not using DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH. DXGI_SWAP_CHAIN_DESC::BufferDesc = { 1680, 1050, { 60, 1 }, R8G8B8A8_UNORM, 0, 0 }; DXGI_SWAP_CHAIN_DESC::SampleDesc = { 1, 0 }; DXGI_SWAP_CHAIN_DESC::Flags = 0;


Presentation inefficiencies!? Oh noes what can this mean! It means that the swapchain is blitting the backbuffer to the front buffer each time I call Present(), instead of page flipping. This isn't what I signed up for!

Luckily the DXGI error itself is full of friendly advice. Am I calling ResizeBuffers appropriately? I'm not calling it at all, I'm creating the window explicitly for fullscreen, then killing it. Am I specifying a MODE_DESC that's not available? Well, R8G8B8A8_UNORM, 1680x1050 at 60Hz is my monitors native display mode, so I didn't think so. Though, the truth is I was. Selecting the same mode from the result set of GetDisplayModeList works fine.

var modes = dxgiFactory.GetAdapter(0).GetOutput(0).GetDisplayModeList(DXGI.Format.R8G8B8A8_UNorm, DXGI.DisplayModeEnumerationFlags.Scaling | DXGI.DisplayModeEnumerationFlags.Interlaced);

DXGI.ModeDescription desc = modes[modes.Count() - 1];


The above code is pretty sloppy, I'll bind the mode collection to a nice combo box and allow the user the choice. It's also hard coded to the primary display adapter, and primary display - which is probably the right choice in most cases, but I'll make this stuff configurable too. It's selecting the highest available mode, which with my hardware happens to be my LCD's native resolution and refresh rate.

Since the output of a NES is so low res, stretching it to a 1680x1050 back buffer is overkill, but nothing the hardware cant handle. Running it in 640x480 mode visually looks exactly the same, as the image is scaled fullscreen when it's presented. I'm considering a little algorithm to look for the first available display mode that has a larger resolution than the last stage in rendering, since it should be the least needed to do the job.

On the topic D3D10/11 surfaces which you can embed in WPF, and overlay with content, it's doable, and I'll play around with another swapchain which renders to a shared resource, which is then presented by D3D9 into WPF's D3DImage. Simple on the surface, but getting it to work properly seems full of little gotchas.

There's no reason to be working in D3D10 anymore, a port to D3D11 should be extremely painless. It's all the same if you aren't using the new stuff. Right now I'm on the fence between continuing down the D3D path, or porting everything over to OpenGL and going that way. It'll likely be the latter, but the last time I tried I had trouble setting it up. Tao Framework doesn't seem to be updated anymore. I think the best way to go would be a native dll for opengl.

Oh, one more thing on the topic of this hybrid fullscreen solution:
dxgiFactory.SetWindowAssociation(fullForm.Handle, DXGI.WindowAssociationFlags.IgnoreAltEnter);


You want to associate your factory with the window it's drawing to, as my switch back to windowed wasn't working right if my current focus was on a different display (ie; my mouse pointer is resting over on the second monitor). I also tell D3D to ignore AltEnter, since I'm handling and calling SetFullScreenState by myself and I don't need it in my face - D3D would put the "hidden" form back into windowed mode and do nothing else.

Thursday, February 25, 2010

D3D 10 Full Screen Shenanigans

So I've been compiling release builds of my emulator lately, and been hosting them here ), and decided to add some full screen support. (sidebar; if anyone knows of some decent free hosting with Frontpage extensions, suitable for hosting ClickOnce packages let me know. It's shocking that not even MSFT's codeplex does

So, since I'm already happily rendering in a HwndHost, inside a WPF form, I figured it'd be a simple call to swapChain.SetFullScreenState(true, null). No such luck! It blowed up and I had to fire up DebugView to see what's going on in there. (sidebar the 2nd; VS 2010 RC doesn't seem to capture the native debug output, perhaps a bug?).

So, what's the story in there?

[5576] IDXGISwapChain::SetFullscreenState: Fullscreen is not allowed for swapchains targetting a child window. Applications are advised to create a second swapchain targetting a non-child window.

Oh yeah? Well scroo yoo too DXGI. I guess that makes sense.

Since I'd been advised to create a second swapchain, I decided that I would do just that. But, it needs to target a non-child window. Which is easy enough, it's a matter of creating a new (WinForms) Form and using it's window handle in the mode description.

So, to create the new swapchain, it's as easy as setting up a DXGI factory and doing the such:


fullSwapChainDescription = new DXGI.SwapChainDescription();
fullSwapChainDescription.ModeDescription = fullDesc;
fullSwapChainDescription.SampleDescription = sampleDescription;
fullSwapChainDescription.BufferCount = 2;
fullSwapChainDescription.Flags = DXGI.SwapChainFlags.None;
fullSwapChainDescription.IsWindowed = false;
fullSwapChainDescription.OutputHandle = myForm.Handle;
fullSwapChainDescription.SwapEffect = DXGI.SwapEffect.Discard;
fullSwapChainDescription.Usage = DXGI.Usage.RenderTargetOutput;

FullSwapChain = new DXGI.SwapChain(dxgiFactory, _device, fullSwapChainDescription);


_device is my D3D device, created earlier.

So, I have 3 SwapChain objects, one Windowed, one FullScreen, and one which holds a reference to the Active one, and this is toggled.

But it's not that easy! Firstly, if I create the new swapchain during app startup, the screen will blank out for a moment. So, I defer it until the first time it is needed. Secondly, switching the swap chain isn't enough. My code is set to render to a resource (texture) which is created from the orignal swap chain. So, the resource and RenderTargetView need to be rebuilt:

if (resouce != null) resource.Dispose();
resource = Texture2D.FromSwapChain(ActiveSwapChain, 0);
RenderTarget = new D3D10.RenderTargetView(_device, resource
);
tileFilters[tileFilters.Count - 1].RenderToTexture(resource);
tileFilters[tileFilters.Count - 1].RenderTarget = RenderTarget;


The last two lines are specific to my app, they set the RenderTarget on the last stage in my rendering chain, thus making it render to the swapchains buffer.

There's more that must be done, the ViewPort should be resized to match the new swapchain.

So, the strangest thing is in switching back to windowed mode. After setting the ActiveSwapChain, it was bombing out of the Toggle method I had wrote, leaving the app in a weird hybrid state. I could only correct it by including a Thread.Sleep(500) in there. I don't like this, obviously. My gut tells me that the emulator is requesting frames to be drawn in the interim, but it really should not be so, as the update calls should be blocked by a lock(this) during the swap.

Well, I'm not a DXGI guru yet, so I guess I'll just keep to the googling and hacking until I figure it out. This does seem to work cleanly, however, despite the icky Sleep call.

Anyone wanting to try my emulator may do so at the link above. If you want to play nintendo games for free, you're probably better off elsewhere. It's still chock full of bugs, the new UI was composed over the last few days from the detritus of fishbulb's past. But, there it is. 10NES.

In other news, if you happen to be looking for a talented coder to join your team in the Baltimore/Washington/Annapolis area(s), feel free to get in touch with me.