views:

743

answers:

2

Hello,

I'm writing an open source 2D game engine, and I want to support as many devices and platforms as possible. I currently only have Windows Mobile though. I'm rendering using DirectX Mobile, with DirectDraw as a fallback path. However, I've run into a bit of trouble.

It seems that while the reference driver supports createRenderTarget, many many many physical devices do not. I need some way to render to the screen without using a render target, because I render sprites using textured quads, but I also need to be able to draw individual pixels.

This is how I do it right now:

// save old values

if (Error::Failed(m_D3DDevice->GetRenderTarget(&m_D3DOldTarget)))
{
    ERROR_EXPLAIN("Could not retrieve backbuffer.");
    return false;
}

// clear render surface

if (Error::Failed(m_D3DDevice->SetRenderTarget(m_D3DRenderSurface, NULL)))
{
    ERROR_EXPLAIN("Could not set render target to render texture.");
    return false;
}

if  (Error::Failed (m_D3DDevice->Clear( 
            0,
            NULL,                        // target rectangle
            D3DMCLEAR_TARGET,
            D3DMCOLOR_XRGB(0, 0, 0),     // clear color
            1.0f, 
            0
        )
    )
)
{
    ERROR_EXPLAIN("Failed to clear render texture.");
    return false;
}

D3DMLOCKED_RECT render_rect;
if (Error::Failed(m_D3DRenderSurface->LockRect(&render_rect, NULL, NULL)))
{
    ERROR_EXPLAIN("Failed to lock render surface pixels.");
}
else
{
    m_D3DBackSurf->SetBuffer((Pixel*)render_rect.pBits);
    m_D3DRenderSurface->UnlockRect();
}

// begin scene

if (Error::Failed(m_D3DDevice->BeginScene()))
{
    ERROR_EXPLAIN("Failed to start rendering.");
    return false;
}

// =====================
// example rendering
// =====================

// some other stuff, but the most important part of rendering a sprite:
device->SetTexture(0, m_Texture));
device->SetStreamSource(0, m_VertexBuffer, sizeof(Vertex));
device->DrawPrimitive(D3DMPT_TRIANGLELIST, 0, 2);

// plotting a pixel
Surface* target = (Surface*)Device::GetRenderMethod()->GetRenderTarget();
buffer = target->GetBuffer();
buffer[somepixel] = MAKECOLOR(255, 0, 0);

// end scene

if (Error::Failed(device->EndScene()))
{
    ERROR_EXPLAIN("Failed to end scene.");
    return false;
}

// clear screen

if (Error::Failed(device->SetRenderTarget(m_D3DOldTarget, NULL)))
{
    ERROR_EXPLAIN("Couldn't set render target to backbuffer.");
    return false;
}

if (Error::Failed(device->GetBackBuffer (   
        0,
        D3DMBACKBUFFER_TYPE_MONO, 
        &m_D3DBack 
        ) 
    ) 
)
{
    ERROR_EXPLAIN("Couldn't retrieve backbuffer.");
    return false;
}

RECT dest = { 
    0, 
    0, 
    Device::GetWidth(), 
    Device::GetHeight()
};

if (Error::Failed( device->StretchRect (        
            m_D3DRenderSurface, 
            NULL,
            m_D3DBack, 
            &dest, 
            D3DMTEXF_NONE 
        )
    )
)
{
    ERROR_EXPLAIN("Failed to stretch render texture to backbuffer.");
    return false;
}

if (Error::Failed(device->Present(NULL, NULL, NULL, NULL)))
{
    ERROR_EXPLAIN("Failed to present device.");
    return false;
}

I'm looking for a way to do the same thing (render sprites using hardware acceleration and plot pixels on a buffer) without using a render target.

Thanks in advance.

A: 

If you want wide compatibility with handheld devices that have no hardware 3d acceleration (every desktop computer for many years has supported render targets), then you might have an easier time using GDI instead of DirectX.

I don't entirely follow what you're trying to do in the code that you posted, but I'll add that render targets do not support LockRect (nor to textures created with the render target flag). DirectX for windows mobile is a bit different from regular DirectX though, so maybe I'm misinterpreting this. If you really want to use DirectX on Windows Mobile, look up CreateOffscreenPlainSurface, or whatever it's managed-DirectX equivalent is. That type of surface would be lockable and would support using StretchRect directly into the back buffer.

Alan
+2  A: 

You didn't specify much about how you wanted to plot pixels, so I'm gonna start out by assuming it's really on the complexity order of "buffer[somepixel] = MAKECOLOR(255, 0, 0);".

My suggestion would be that instead of reading back (which might be very expensive or quite cheap depending on memory architecture), modifying, and writing/uploading the backbuffer; Instead, plot your pixels to a texture, and let the GPU handle the composition of the GPU accelerated sprites and the CPU plotted pixels.

In the basic case, that would be something like:

  1. Create a 4-channel texture the size of your backbuffer. If your target system supports it, specify LOCKABLE and DYNAMIC usage flags. (If it doesn't, or if the driver prefers non-lockable textures, you'll have to go through an extra system texture for step 2 below. And upload it using UpdateTexture / CopyRects instead)

  2. Each frame, lock the texture, plot your pixels, unlock the texture. Make sure to write coverage information to the alpha channel of the pixel, so that the GPU has some data that can control the composition. E.g. if you wanted to plot solid red, write (255, 0, 0, 255) for that pixel. Make sure you clear the pixels you don't actually want to plot.

  3. Set up the GPU to compose your plotted data on top of your sprites. by rendering a full-screen quad textured with your plotted texture (and using point sampling). For the simplest opaque composition case, enable AlphaTestEnable, set AlphaRef to 128, and AlphaFunc to GREATER, and disable ZEnable. This will render the plotted pixels on top of the sprites, while leaving the rest of the sprite pixels untouched.

If you're using a depth buffer, and you wanted to get fancy about gaining some extra performance, you could render the plotted texture before your sprites, with both ZEnable and ZWriteEnabled enabled. (Make sure you render the full-screen quad at the near plane then) This will fill in a depth buffer mask where your opaque pixels are, which will occlude those pixels when you later render the sprites.

Obviously, you're not really constrained to just overwriting solid pixels either. You could use alpha blending with a combination of creative blend modes and alpha values to create other kinds of compositions and overlays.

tbone
This is an excellent reply! However, your method is still problematic. In a perfect world, I would render a sprite, a line over that sprite and then another sprite. The line would be drawn between the two sprites according to the painter's algorithm. The only way to get this result (as far as I can tell) is to render each line to its own texture. One method that kind of works is to render directly to the backbuffer, however my target device doesn't support that EITHER. :\
knight666
What's stopping you from rendering your lines using DX as well, e.g. as diffusely colored quads? Or, if you need them to have detailed pixel information, perhaps you could create textures at application start, and only render with them later? If your sprites are opaque (or masked), the easiest way to ensure correct sorting is to just use a depth buffer. If they're blended/translucent, then yeah, you need to sort them back to front, and interleave them with your opaque lines (or have the lines write depth up front, and the sprites test depth)
tbone