Trending
Opinion: How will Project 2025 impact game developers?
The Heritage Foundation's manifesto for the possible next administration could do great harm to many, including large portions of the game development community.
In this reprinted <a href="http://altdevblogaday.com/">#altdevblogaday</a>-opinion piece, Squared Interactive's Kevin Gadd demonstrates how you can copy pixel data from a pointer into an XNA texture using C#, PInvoke, and some knowledge of XNA internals.
[In this reprinted #altdevblogaday-opinion piece, Squared Interactive's Kevin Gadd demonstrates how you can copy pixel data from a pointer into an XNA texture using C#, PInvoke, and some knowledge of XNA internals.] In the XNA framework, the only way to load pixel data into a texture is to provide it in the form of a .NET Array. In most cases, this isn't a problem ā you can specify an offset into the array, and the array can be of any type. However, if you're using a library like Awesomium or Berkelium, you will end up with large blocks of pixel data in the form of a pointer. The only way to turn this into an array is to manually allocate an array of the appropriate size and copy the pixels over every time they change. Not only does this waste memory, but it's relatively expensive. In this post I'll show you a (fairly evil) way to copy pixel data directly from a pointer into an XNA texture using C#, PInvoke, and some knowledge of XNA internals. The first step is to understand how XNA's Texture2D.SetData method works normally. I opened it up in ILSpy, a free .NET decompiler. Looking at the three overloads of SetData reveals that they all call into a private method called CopyData, with this signature:
private unsafe void CopyData<T>(int level, Rectangle? rect, T[] data, int startIndex, int elementCount, uint options, [MarshalAs(UnmanagedType.U1)] bool isSetting) where T : struct
The method is large and complicated, and parts of it don't decompile into readable, valid C#. But we can understand what it does and how it does it with some research and careful examination of the decompiled code. The first part of the function spends a bunch of time validating all the provided arguments. It ensures that the texture being modified isn't active as a render target or attached to one of the device's texture samplers, and validates other parameters like the size of the texture and the size of the data provided. However, you start to see rather confusing code like this in the disassembly:
_D3DSURFACE_DESC d3DSURFACE_DESC; initblk(ref d3DSURFACE_DESC, 0, 32); IDirect3DTexture9* ptr = this.pComPtr; int num3 = calli(System.Int32 modopt(System.Runtime.CompilerServices.IsLong) modopt(System.Runtime.CompilerServices.CallConvStdcall) (System.IntPtr,System.UInt32,_D3DSURFACE_DESC*), ptr, level, ref d3DSURFACE_DESC, *(*(int*)ptr + 68)); if (num3 < 0) { throw GraphicsHelpers.GetExceptionFromResult(num3); }
If you know enough about .NET bytecode (CIL/MSIL), it may be easier to understand what's going on here. Bytecodes that couldn't be mapped to C# constructs were basically spit out directly as if those bytecodes were functions. This is happening because XNA is mostly written in C++/CLI, not C#. First, the initblk opcode is used to zero-initialize the contents of a new D3DSURFACE_DESC structure. This is equivalent to doing a memset in native C/C++. Next, the calli opcode is used. Essentially, this opcode allows you to invoke a native function directly, given a function pointer and knowledge of the function's signature. All of the decompiled code here is essentially describing the signature of the function and then providing arguments for it. You'll probably never see this generated by C# code, but it's used often in C++/CLI to invoke native functions - and in this case, it's being used to invoke a method of a COM interface. How can you tell it's being used to invoke a method of a COM interface? There are a few clues here: First, we notice that ptr contains a pointer of type IDirect3DTexture9 - a COM interface. Second, we can look at the argument list to the calli instruction:
System.Int32 modopt(System.Runtime.CompilerServices.IsLong) modopt(System.Runtime.CompilerServices.CallConvStdcall) (System.IntPtr,System.UInt32,_D3DSURFACE_DESC*), ptr, level, ref d3DSURFACE_DESC, *(*(int*)ptr + 68));
First we have the return type of the function - System.Int32. Next, two modifiers that notify the compiler about the nature of the function - it's a stdcall, and the return type is a C++ 'long' with the semantics that implies. This doesn't really matter much to us at the moment, but it's good to understand the basics. Next, the argument types for the function are provided: System.IntPtr, System.UInt32, and a D3DSURFACE_DESC *. Given this information, we now know the signature of the function being called, so we could write an equivalent delegate if we wanted. The decompilation doesn't tell us the name of the function, but given the name of the interface (IDirect3DTexture9) and the argument list, we can check that interface's documentation on MSDN and try to figure out which function it is. Finally, let's look at the actual arguments being passed when invoking the function:
ptr, level, ref d3DSURFACE_DESC, *(*(int*)ptr + 68)
First, we see the first three arguments are of the appropriate type for the function's signature and their values make sense. There's a fourth argument, though, and it looks funny - in fact, it looks like the decompiler didn't quite make sense of it. What is that? Well, we know that ptr is a pointer to an IDirect3DTexture9. First, the code is dereferencing the pointer. If you know enough about COM, you will realize that dereferencing a pointer to a COM interface will allow you to access that interface's vtable. The vtable contains pointers to each function provided by the interface, which allows you to invoke those functions on a given instance. Given that the code is dereferencing the interface pointer and then adding an offset to it, we can now infer that it's pulling a specific function out of the interface's VTable. Again, we can't immediately tell which function, but we have a lot of information here that we could use to figure it out. This makes it pretty clear that a COM function is being called on the interface, because we can see the function pointer being pulled out of the COM vtable and passed to the calli instruction, along with a function signature and argument types that seem like a perfect fit for a COM method (HRESULT return value, first argument is the interface pointer). At this point, we could dig through the vtable and find our way to the necessary method calls to lock the texture and get a pointer to its pixels. Then we could do any necessary pixel format conversion, etc while copying from our buffer to the texture. However, there's an easier solution! If you read through the code for the method, in certain code paths, it makes use of a D3DX function called D3DXLoadSurfaceFromSurface. It's a pretty useful function - it can convert pixel formats, resample textures, and even handle DXTC compression. And if you look through the documentation, there's a version of this function that can take a pointer as a source instead of another surface - making it perfect for our needs. We just have to find a way to call that function and hand it our image data pointer and D3DX will handle any necessary pixel format conversion and copy our pixel data to the texture. Now, to call the function. First, we need to get ourselves an interface pointer. If we're willing to use reflection, this is quite simple - we can get ourselves a reference to the pComPtr field we see used in the disassembly and use that reference to get the interface pointer for a given texture, like so:
public static class TextureUtils { internal static FieldInfo pComPtr; static TextureUtils () { pComPtr = typeof(Texture2D).GetField("pComPtr", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic); } public static unsafe void* GetIDirect3DTexture9 (this Texture2D texture) { return Pointer.Unbox(pComPtr.GetValue(texture)); }
Next, we can set up the necessary P/Invoke skeleton to be able to invoke the D3DX function. We need two enumerations, a struct, and finally the function itself:
// Some enumeration members omitted for readability public enum D3DFORMAT : uint { UNKNOWN = 0, R8G8B8 = 20, A8R8G8B8 = 21, X8R8G8B8 = 22, A8B8G8R8 = 32, X8B8G8R8 = 33, } [Flags] public enum D3DX_FILTER : uint { DEFAULT = 0xFFFFFFFF, NONE = 0x00000001, POINT = 0x00000002, LINEAR = 0x00000003, } [StructLayout(LayoutKind.Sequential)] public struct RECT { public int Left; public int Top; public int Right; public int Bottom; } // Note that we need to be careful to use the same exact version of D3DX that XNA uses. [DllImport("d3dx9_41.dll")] private static unsafe extern int D3DXLoadSurfaceFromMemory ( void* pDestSurface, void* pDestPalette, RECT* pDestRect, void* pSrcMemory, D3DFORMAT srcFormat, uint srcPitch, void* pSrcPalette, RECT* pSrcRect, D3DX_FILTER filter, uint colorKey );
At this point, however, you might have realized the problem: This function takes a pointer to an IDirect3DSurface9, not an IDirect3DTexture9. How do we go from a Texture to a Surface? Well, the MSDN documentation shows that there's a convenient method that will give us the surface pointer for a given mip level of a texture: GetSurfaceLevel. But given that all we have is a void *, how can we call it? There are a few ways, of course - you could write a tiny C++ library to do the work, or you could add a reference to the Managed DirectX libraries, or something like that. But we're going to do it the way they do it in C - with pointers! To make sure we're on the right track, we can first dig through the disassembly of CopyData to find the spot where it calls GetSurfaceLevel. Since we know the argument types and return type, it's not too hard to find:
IDirect3DSurface9* ptr3; IDirect3DTexture9* ptr4; num5 = calli(System.Int32 modopt(System.Runtime.CompilerServices.IsLong) modopt(System.Runtime.CompilerServices.CallConvStdcall) (System.IntPtr,System.UInt32,IDirect3DSurface9**), ptr4, 0, ref ptr3, *(*(int*)ptr4 + 72)); if (num5 >= 0) {
With what we know about COM method invocations, we can tell this is invoking a method of IDirect3DTexture9 with an integer parameter (0) and a pointer-to-pointer parameter of type IDirect3DSurface9. This is definitely GetSurfaceLevel. This not only tells us what the right signature for the function looks like, but it also tells us where in the interface's VTable we can find a pointer to the function in order to call it. So, armed with this knowledge, we can write some terrifying unsafe code to pull the function pointer out of the VTable:
// This could be written better, probably. I'm lazy. public static unsafe void* AccessVTable (void* pInterface, uint offsetInBytes) { void* pVTable = (*(void**)pInterface); return *((void**)((ulong)pVTable + offsetInBytes)); }
And, using that function along with an appropriate delegate type, we can put these pieces together to get a surface pointer:
internal unsafe delegate int GetSurfaceLevelDelegate (void* pTexture, uint iLevel, void** pSurface); public static class VTables { public static class IDirect3DTexture9 { public const uint GetSurfaceLevel = 72; } } public static unsafe void* GetSurfaceLevel (this Texture2D texture, int level) { void* pTexture = texture.GetIDirect3DTexture9(); void* pGetSurfaceLevel = AccessVTable(pTexture, VTables.IDirect3DTexture9.GetSurfaceLevel); void* pSurface; var getSurfaceLevel = (GetSurfaceLevelDelegate)Marshal.GetDelegateForFunctionPointer( new IntPtr(pGetSurfaceLevel), typeof(GetSurfaceLevelDelegate) ); var rv = getSurfaceLevel(pTexture, 0, &pSurface); if (rv == 0) return pSurface; else throw new COMException("GetSurfaceLevel failed", rv); }
We use the marshaling API to turn the function pointer into a callable delegate with the appropriate signature, and then call it in order to get ourselves an interface pointer. Now, we're finally ready to call D3DX! Let's wrap this magic up with a helpful function:
public static unsafe void SetData ( this Texture2D texture, int level, void* pData, int width, int height, uint pitch, D3DFORMAT pixelFormat ) { var rect = new RECT { Top = 0, Left = 0, Right = width, Bottom = height }; void* pSurface = GetSurfaceLevel(texture, level); try { var rv = D3DXLoadSurfaceFromMemory(pSurface, null, &rect, pData, pixelFormat, pitch, null, &rect, D3DX_FILTER.NONE, 0); if (rv != 0) throw new COMException("D3DXLoadSurfaceFromMemory failed", rv); } finally { Release(pSurface); } }
You'll note that we're also ensuring that we decrement the reference count on the surface after we're done with it so it doesn't leak. To do this, we wrote a quick helper function:
internal unsafe delegate uint ReleaseDelegate (void* pObj); public static class VTables { public static class IUnknown { public const uint Release = 8; } } public static unsafe uint Release (void* pObj) { void* pRelease = AccessVTable(pObj, VTables.IUnknown.Release); var release = (ReleaseDelegate)Marshal.GetDelegateForFunctionPointer (new IntPtr(pRelease), typeof(ReleaseDelegate)); return release(pObj); }
And now, after all that hard work, copying pixel data from an Awesomium WebView into an XNA texture is as simple as this:
var rb = WebView.Render(); WebViewTexture = new Texture2D(GraphicsDevice, rb.Width, rb.Height, false, SurfaceFormat.Color); WebViewTexture.SetData(0, rb.Buffer.ToPointer(), rb.Width, rb.Height, (uint)rb.Rowspan, D3DFORMAT.A8R8G8B8);
If you'd like to use this for yourself, you can download it in a ready-to-use form from my GitHub repository. Hope it's helpful! [This piece was reprinted from #AltDevBlogADay, a shared blog initiative started by @mike_acton devoted to giving game developers of all disciplines a place to motivate each other to write regularly about their personal game development passions.]
You May Also Like