Search this blog

Loading...

Monday, May 13, 2013

I'll create a new trend...

Typography (Helvetica) on Pictures about Technical stuff. My next presentation will be all like this. Oh, shit, I should have added cats too.

In all seriousness though. This "poster" tells the truth, I didn't realize, until recently, how important this lesson is. And how important is, when you follow it, to pick the right thing to specialize upon (as you won't be able to change it much later on...).

I've seen it in the CouchDB guide, where the citation is attributed to Joe Stump. Premature generalization is the root of all evil...

Scaling is Specialization.

Integrating C++11 in your diet

Even for a guy like me who despises C++ and is happy to escape from it as often as possible, the reality of daily work still involves mostly C++ programming.

Being "good" at C++ is mostly a matter of having a good diet. Of course you try to write "sane" C++, staying C as much as possible, using a "safe" subset of the language (1 2 3 etc...), using static code checkers (vs2012 analysis at least, even if I've found it to be quite lax) and so on, these things have been written over an over. The bottom line is, you find your subset of things that are usable and of rules that never should be broken.

Now, parts of the new C++11 standard are coming into mainstream compilers (read, Visual Studio 2012) and so I had to update my "diet" to incorporate a few new, useful features (mostly C++ trying to look like C#, which ain't bad).

This is my small list of things you should consider to start using (at least on PC, for tools etc...).
  • Use today:
    • auto - Variable type inference. Really, makes a big difference in readability and it's essential for things like stl iterators and so on.
    • lambdas - Reasonably simple, better than function pointers, and also support closures which are the real deal. As C++ doesn't have garbage collection they have restrictions lambdas in other languages don't face, that's to say, you have to think of how you capture things and what are their lifetimes, but it's something we're used to by now. Still you might want to fallback to regular functor objects when you need to make more explicit what you're doing in the "capturing" constructor/destructor. Be sure to know what they really are (typeless objects on the stack)
    • range based for - int array[5] = { 1, 2, 3, 4, 5 }; for (int& x : array)... Small, but saves some typing and every other language does have it...
    • override and final for virtual functions. Maybe in then years we'll even have "out" for non-const reference/pointer parameters...
  • Good, but not yet widespread (non implemented by VS2012):
    • non-static member initializer - The ability to initialize member variables at the point of declaration, instead of having to add code to your constructors
    • constexpr - Compile-time constant expressions. Could be nifty, i.e. can remove the need of hacks to do compiletime conversion of strings to hashes...
    • delegating constructors - A small addition, calling constructors from initializer lists of other constructors, it's useful but we already have workarounds and anyhow, you should really initialize things outside your constructor and never use exceptions. Even less interesting is constructor inheritance
    • Unrestricted unions - Will enable having unions of types with non-trivial constructors which are not allowed today. No new syntax == good
    • Sizeof of member variables without an instance - The lack of this is really counterintuitive and maddening 
  • Questionable/good to know/shouldn't use often:
    • Tl;Dr; don't use anything that adds more rules/alternative syntax for things that can be done already. Don't use templates, especially if you think you really found a cool way to use them (i.e. for anything that does not have to do with collections). Don't read Alexandrescu. Don't be smart.
    • initializer lists - These are nice, but they add more ways/rules to the resolution of constructors which is never great, function resolution rules in C++ are already way too complex.
    • R-value references - They generated a lot of noise and you probably know about them (surely, you'll need to know about them), they do make a big difference in the STL (see this for an introduction) but the truth is, you probably already are careful to avoid temporaries (or objects!) and you don't do much work in your constructors... This is mostly good news for the STL and for the rare reasonable uses of templates (unfortunately, we didn't get concepts... so yes, C++ templates are still awful). They are complex. And that is NOT good, C++ is already obscure enough.
    • Extern templates - Could reduce code bloat due to templates by not having them instantiated in all translation units. It doesn't mean you don't have to have all your templates in your headers though, it's a bit of a mess to use. You shouldn't use many templates anyhow, right?
    • Explicitly deleting or defaulting auto-generated class functions - Today, you should always remember to declare the functions C++ currently automatically implements for classes (private without implementation if you're not implementing them). This new extension will make that somewhat easier.
    • Explicit conversion operator - Patches an ugly hole in the language with implicit conversions. You should ban all the implicit conversions (don't implement custom cast operators and mark all constructors as explicit) anyways and always use member functions instead, so you shouldn't find yourself needing it often...
    • typed enums - This is actually nice, but it adds yet more things to remember to the language, I'm undecided. The main good part of it is that typed enums don't automatically cast to integers (remember that vice-versa is already not true)
  • Minor:
    • __func__ - Officially added to the existing __FILE__ and __LINE__
    • Minimal GC support - You're not likely going to use this, but it's good-to-know...
    • no_except. You shoulnd't use exceptions anyways...
    • static_assert - Chances are that you already know what this is and have macros defined
    • alignment - Chances are that you already have some compiler-dependent macros defined...
    • decltype - "Grabs" a type from an expression, fixes some old problems with templates, chances are that you'll never run into this other than some questionable uses in typedef decltype(expression)
    • nullptr - Fairly minor, tl;dr NULL is now (also) called nullptr, which is a little bit better
    • foward declaration of enums - Fairly minor, does what it says
I've left out the new library features. C++11 introduced support for concurrency (atomics, threading support, fences, tasks, futures etc...) new smart pointers (unique/shared/weak with their corresponding "make" functions), unordered_map container (hashtable) and so on, they are all nice and much needed functions but chances are you already have rolled your own, optimized versions over these years which could still be even better than what the early compilers will provide on a given platform.

C++ as a language is still so much behind on what matters for performance (regardless of Bjarne's wet dreams), we are and we will still be crafting our own stuff/relying on complier extensions and intrinsics. We did well with that, we'll do well still.

Rant (can't be avoided when I write about C++): You'll be hearing (or already heard) a lot about "modern" C++, referring to C++11. It's a marketing lie, as most of what they did. Fundamentally, C++11 does not address any of the big issues C++ suffers from (bad defaults, pitfalls, half-arsed templates etc... basically the SIZE of the language and the quality of it), instead it's mostly concerned with "catching up" the back of the box feature list (and making an half-arsed attempt at that, as most things can't be done properly anyways...).
It doesn't even attempt to deprecate anything, it managed to kill the most useful features devoted at simplifying it (template concepts!), it adds a TON of new syntax while keeping the old defaults (no_except, the controls for automatic class functions...) thus hoping that you just remember to use it, and it adds a TON of features squarely aimed at crazy-template-metaprogramming users that most sane people will never allow anyways.
We don't do obfuscated C++ contests because it would be to easy already, with C++11, it would become really crazy...

If you want a full overview, see:

Peek'n'Poke

Sometimes I write tools small and stupid enough to be contained in a blog post. This in one of them...

I always wanted to have graphical visualizers inside visual studio, to see matrices, points, images and such things from raw memory locations. It turns out that's very simple if you just ReadProcessMemory from an external tool, even simpler than writing a Visual Studio extension. Of course, this doesn't work when remote debugging (and the simplest option there would be to write a server or just something intrusive in the code). 



This small C# sample does display images from a process memory, refreshing every 33ms, it supports a few formats (r8 is broken as I was too lazy to set the palette, expect bugs in general...) but it could be easily extended to do whatever you need (i.e. graph floats in time...). 

Enjoy!

P.S. If you extend/fix/find anything incredibly dumb in the code below, leave a comment! Thanks...

In the future, it would be really cool to have a dynamic debugging/program visualization tool. There is already quite some work, also if you look in the reversing/hacking community.


Update: Now with floating point images support and endian swaps...


using System;
 
namespace Peek
{
    class Program
    {
#region Kernel Imports
        // http://msdn.microsoft.com/en-us/library/windows/desktop/ms684880(v=vs.85).aspx
        const uint ACL_DELETE = 0x00010000;
        const uint ACL_READ_CONTROL = 0x00020000;
        const uint ACL_WRITE_DAC = 0x00040000;
        const uint ACL_WRITE_OWNER = 0x00080000;
        const uint ACL_SYNCHRONIZE = 0x00100000;
        const uint ACL_END = 0xFFF; //if you have Windows XP or Windows Server 2003 you must change this to 0xFFFF
        const uint PROCESS_VM_READ = 0x0010;
        const uint PROCESS_VM_WRITE = 0x0020;
        const uint PROCESS_VM_OPERATION = 0x0008;
        const uint PROCESS_ALL_ACCESS = (ACL_DELETE | ACL_READ_CONTROL | ACL_WRITE_DAC | ACL_WRITE_OWNER | ACL_SYNCHRONIZE | ACL_END);
 
        [System.Runtime.InteropServices.DllImport("kernel32.dll")]
        static extern uint OpenProcess(uint dwDesiredAccess, bool bInheritHandle, int dwProcessId);
        [System.Runtime.InteropServices.DllImport("kernel32.dll")]
        static extern bool ReadProcessMemory(uint hProcess, UIntPtr lpBaseAddress, byte[] buffer, uint size, uint lpNumberOfBytesRead);
        [System.Runtime.InteropServices.DllImport("kernel32.dll")]
        static extern bool ReadProcessMemory(uint hProcess, UIntPtr lpBaseAddress, IntPtr buffer, uint size, uint lpNumberOfBytesRead);
        [System.Runtime.InteropServices.DllImport("kernel32.dll")]
        static extern bool WriteProcessMemory(uint hProcess, UIntPtr lpBaseAddress, byte[] buffer, uint size, uint lpNumberOfBytesWritten);
        [System.Runtime.InteropServices.DllImport("kernel32.dll")]
        static extern bool WriteProcessMemory(uint hProcess, UIntPtr lpBaseAddress, IntPtr buffer, uint size, uint lpNumberOfBytesWritten);
 
        static byte[] ReadMemory(UIntPtr address, uint size, uint processHandle)
        {
            byte[] buffer = new byte[size];
            ReadProcessMemory(processHandle, address, buffer, size, 0);
            return buffer;
        }
        class UnmanagedMemWrapper // should we GC.AddMemoryPressure?
        {
            public UnmanagedMemWrapper(uint size)
            {
                this.ptr = System.Runtime.InteropServices.Marshal.AllocHGlobal((int)size);
            }
            ~UnmanagedMemWrapper()
            {
                System.Runtime.InteropServices.Marshal.FreeHGlobal(ptr);
            }
            
            public IntPtr ptr;
        }
#endregion // Kernel Imports
 
 
        // Utility, half2float, could use DirectXMath DirectX::PackedVector functions instead...
        [System.Runtime.InteropServices.DllImport("d3dx9_35.dll")]
        public static extern void D3DXFloat16To32Array(float[] output, IntPtr input, uint nfloats);
 
        static void PrintUsageAndErrors(string error)
        {
            System.Console.WriteLine("Peek");
            System.Console.WriteLine("----");
            System.Console.WriteLine();
            System.Console.WriteLine("Arguments: process name, instance number, pointer address, [peek mode]");
            System.Console.WriteLine("Note that multiple processes can have the same name...");
            System.Console.WriteLine();
            System.Console.WriteLine("Peek mode:");
            System.Console.WriteLine(" img [format] xsize ysize -- draws a 2d image");
            System.Console.WriteLine("  Supported formats: argb8 argb16 rgb8 rgb16 r8 r16 argb32f argb16f rgb32f rgb16f r32f r16f");
            System.Console.WriteLine();           
 
            if(error.Length!=0)
            {
                System.Console.WriteLine("Error!");
                System.Console.WriteLine(error);
            }
        }
 
        [STAThreadstatic void Main(string[] args)
        {
            if (args.Length < 7)
            {
                PrintUsageAndErrors("Not enough arguments"); return;
            }
 
            var procs = System.Diagnostics.Process.GetProcessesByName(args[0]);
            UInt32 procNumber = 0;
 
            if (!UInt32.TryParse(args[1], out procNumber))
            {
                PrintUsageAndErrors("Can't parse process number"); return;
            }
 
            if (procs.Length <= procNumber)
            {
                PrintUsageAndErrors("Process instance not found"); return;
            }
 
            var proc = procs[procNumber];
            uint procHandle = OpenProcess(PROCESS_VM_READ, false, proc.Id);
 
            if (procHandle == 0)
            {
                PrintUsageAndErrors("Failed to open process"); return;
            }
 
            switch (args[3])
            {
                case "img":
                    {
                        UInt32 xsize, ysize;
                        if ((!UInt32.TryParse(args[5], out xsize)) || (!UInt32.TryParse(args[6], out ysize)))
                        {
                            PrintUsageAndErrors("Can't parse img size"); return; 
                        }
 
                        switch (args[4])
                        {
                            case "argb8":
                                PeekImg(procHandle, args[2], xsize, ysize, 4, System.Drawing.Imaging.PixelFormat.Format32bppArgb, ImgOP.NONE);
                                break;
                            case "rgb8":
                                PeekImg(procHandle, args[2], xsize, ysize, 3, System.Drawing.Imaging.PixelFormat.Format24bppRgb, ImgOP.NONE);
                                break;
                            case "argb16":
                                PeekImg(procHandle, args[2], xsize, ysize, 8, System.Drawing.Imaging.PixelFormat.Format64bppArgb, ImgOP.NONE);
                                break;
                            case "rgb16":
                                PeekImg(procHandle, args[2], xsize, ysize, 6, System.Drawing.Imaging.PixelFormat.Format48bppRgb, ImgOP.NONE);
                                break;
                            case "r8":
                                PeekImg(procHandle, args[2], xsize, ysize, 1, System.Drawing.Imaging.PixelFormat.Format8bppIndexed, ImgOP.NONE);
                                break;
                            case "r16":
                                PeekImg(procHandle, args[2], xsize, ysize, 2, System.Drawing.Imaging.PixelFormat.Format16bppGrayScale, ImgOP.NONE);
                                break;
                            case "argb32f":
                                PeekImg(procHandle, args[2], xsize, ysize, 4, System.Drawing.Imaging.PixelFormat.Format32bppArgb, ImgOP.F32_TO_I8);
                                break;
                            case "rgb32f":
                                PeekImg(procHandle, args[2], xsize, ysize, 3, System.Drawing.Imaging.PixelFormat.Format24bppRgb, ImgOP.F32_TO_I8);
                                break;
                            case "argb16f":
                                PeekImg(procHandle, args[2], xsize, ysize, 4, System.Drawing.Imaging.PixelFormat.Format32bppArgb, ImgOP.F16_TO_I8);
                                break;
                            case "rgb16f":
                                PeekImg(procHandle, args[2], xsize, ysize, 3, System.Drawing.Imaging.PixelFormat.Format24bppRgb, ImgOP.F16_TO_I8);
                                break;
                            case "r32f":
                                PeekImg(procHandle, args[2], xsize, ysize, 1, System.Drawing.Imaging.PixelFormat.Format8bppIndexed, ImgOP.F32_TO_I8);
                                break;
                            case "r16f":
                                PeekImg(procHandle, args[2], xsize, ysize, 1, System.Drawing.Imaging.PixelFormat.Format8bppIndexed, ImgOP.F16_TO_I8);
                                break;
                            default:
                                PrintUsageAndErrors("Unknown image format");
                                return;
                        }
                        
                        break;
                    }
                default:
                    PrintUsageAndErrors("Unknown peek options");
                    return;
            }
        }
 
        class DBForm : System.Windows.Forms.Form
        {
            public DBForm()
            {
                DoubleBuffered = true;
            }
        }
 
        enum ImgOP { NONE, F16_TO_I8, F32_TO_I8 }
        static void PeekImg(
            uint procHandle, string ptrString, UInt32 xsize, UInt32 ysize, UInt32 bytesPP,
            System.Drawing.Imaging.PixelFormat format, ImgOP imgOp = ImgOP.NONE
        )
        {
            uint imageSize = 0;
            uint readSize = 0;
            float[] tempHalfToFloatMemory = null;
            UnmanagedMemWrapper unmanagedMemory = null;
            UIntPtr pointer = new UIntPtr(0);
 
            using (var form = new DBForm() { Text = "Peeker" })
            {
                form.SetBounds(0, 0, xsize > 400 ? (int)xsize : 400, (int)ysize + 100);
 
                // Meh, there was no reason to do all this by hand really, also, I could have added everything to the DBForm class...
                var timer = new System.Windows.Forms.Timer() { Interval = 33, Enabled = true };
                var background = new System.Drawing.Drawing2D.HatchBrush(
                    System.Drawing.Drawing2D.HatchStyle.LargeCheckerBoard, System.Drawing.Color.Black, System.Drawing.Color.White);
                System.Drawing.Bitmap bitmap = null;
                var noAlphaButton = new System.Windows.Forms.CheckBox() { Text = "NoAlpha", Left = 0, Width = 70 };
                var endianSwapButton = new System.Windows.Forms.CheckBox() { Text = "Endian", Left = 70, Width = 70 };
                var RBSwapButton = new System.Windows.Forms.CheckBox() { Text = "RB Swap", Left = 140, Width = 70 };
                var memControl = new System.Windows.Forms.TextBox() { Text = ptrString, Left = 210, Width = 210 };
                var xresControl = new System.Windows.Forms.NumericUpDown() { Minimum = 0, Maximum = 9999, Value = xsize, Top = 25, Left = 0, Width = 105 };
                var yresControl = new System.Windows.Forms.NumericUpDown() { Minimum = 0, Maximum = 9999, Value = ysize, Top = 25, Left = 105, Width = 105 };
                var resetButton = new System.Windows.Forms.Button() { Text = "Reinit", Top = 25, Left = 260 };
 
                form.Controls.Add(memControl);
                form.Controls.Add(noAlphaButton); form.Controls.Add(endianSwapButton); form.Controls.Add(RBSwapButton);
                form.Controls.Add(xresControl); form.Controls.Add(yresControl); form.Controls.Add(resetButton);
 
                if (format != System.Drawing.Imaging.PixelFormat.Format32bppArgb)
                {   // For now these conversions are coded only for argb8 and formats that decode into argb8
                    noAlphaButton.Enabled = false;
                    endianSwapButton.Enabled = false;
                    RBSwapButton.Enabled = false;
                }
 
                resetButton.Click += delegate(object sender, System.EventArgs e)
                {   // This is and ugly hack just because the delegate/event system in C# is a bit restrictive...
                    InitBuffers(bytesPP, format, imgOp, ref imageSize, ref readSize, ref tempHalfToFloatMemory, 
                        ref unmanagedMemory, ref bitmap, ref pointer, ref memControl, xresControl, yresControl);
                };
                InitBuffers(bytesPP, format, imgOp, ref imageSize, ref readSize, ref tempHalfToFloatMemory,
                    ref unmanagedMemory, ref bitmap, ref pointer, ref memControl, xresControl, yresControl);
 
                form.Paint += delegate(object sender, System.Windows.Forms.PaintEventArgs e)
                {
                    if (unmanagedMemory == null)
                        return;
 
                    ReadProcessMemory(procHandle, pointer, unmanagedMemory.ptr, readSize, 0);
 
                    if (imgOp == ImgOP.F16_TO_I8)
                    {
                        D3DXFloat16To32Array(tempHalfToFloatMemory, unmanagedMemory.ptr, (uint)tempHalfToFloatMemory.Length);
                        unsafe
                        {
                            fixed (float* floatPtr = tempHalfToFloatMemory)
                            {
                                byte* bytePtr = (byte*)unmanagedMemory.ptr;
                                for (int i = 0; i < imageSize; i++)
                                {
                                    float scaledVal = floatPtr[i] * 255.0f;
                                    bytePtr[i] = (byte)(scaledVal > 255.0f ? 255.0f : scaledVal);
                                }
                            }
                        }
                    }
                    else if (imgOp == ImgOP.F32_TO_I8)
                    {
                        unsafe
                        {
                            byte* bytePtr = (byte*)unmanagedMemory.ptr;
                            float* floatPtr = (float*)unmanagedMemory.ptr;
                            for (int i = 0; i < imageSize; i++)
                            {
                                float scaledVal = floatPtr[i] * 255.0f;
                                bytePtr[i] = (byte)(scaledVal > 255.0f ? 255.0f : scaledVal);
                            }
                        }
                    }
 
                    if (noAlphaButton.Checked && endianSwapButton.Checked)
                    {
                        unsafe
                        {
                            byte* bytePtr = (byte*)unmanagedMemory.ptr;
                            for (int i = 0; i < imageSize; i += 4)
                            {
                                bytePtr[i] = bytePtr[i + 3];
                                bytePtr[i + 3] = 255;
                                byte temp = bytePtr[i + 2];
                                bytePtr[i + 2] = bytePtr[i + 1];
                                bytePtr[i + 1] = temp;
                            }
                        }
                    }
                    else if (noAlphaButton.Checked)
                    {
                        unsafe
                        {
                            byte* bytePtr = (byte*)unmanagedMemory.ptr;
                            for (int i = 0; i < imageSize; i += 4)
                                bytePtr[i+3] = 255;
                        }
                    }
                    else if (endianSwapButton.Checked)
                    {
                        unsafe
                        {
                            byte* bytePtr = (byte*)unmanagedMemory.ptr;
                            for (int i = 0; i < imageSize; i += 4)
                            {
                                byte temp = bytePtr[i + 3];
                                bytePtr[i + 3] = bytePtr[i];
                                bytePtr[i] = temp;
                                temp = bytePtr[i + 2];
                                bytePtr[i + 2] = bytePtr[i + 1];
                                bytePtr[i + 1] = temp;
                            }
                        }
                    }
 
                    if (RBSwapButton.Checked) // Loop again, I don't want to code all the variants...
                    {
                        unsafe
                        {
                            byte* bytePtr = (byte*)unmanagedMemory.ptr;
                            for (int i = 0; i < imageSize; i += 4)
                            {
                                byte temp = bytePtr[i + 2];
                                bytePtr[i + 2] = bytePtr[i];
                                bytePtr[i] = temp;
                            }
                        }
                    }
 
                    //var data = bitmap.LockBits(rect, System.Drawing.Imaging.ImageLockMode.WriteOnly, bitmap.PixelFormat);
                    //System.Diagnostics.Debug.Assert(data.Scan0 == unmanagedPtr.ptr);
                    //bitmap.UnlockBits(data);
 
                    // Draw a pattern to be able to "see" alpha...
                    e.Graphics.FillRectangle(background, 0, 0, form.Bounds.Width, form.Bounds.Height);
                    e.Graphics.DrawImage(bitmap, new System.Drawing.Point(0, 60));
                };
 
                timer.Tick += delegate(object sender, EventArgs e)
                {
                    form.Refresh();
                };
 
                // Run...
                System.Windows.Forms.Application.EnableVisualStyles();
                form.Show(); form.Focus(); timer.Start();
                System.Windows.Forms.Application.Run(form);
            }
        }
 
        private static void InitBuffers(UInt32 bytesPP, System.Drawing.Imaging.PixelFormat format, ImgOP imgOp, 
            ref uint imageSize, ref uint readSize, ref float[] tempHalfToFloatMemory, ref UnmanagedMemWrapper unmanagedMemory,
            ref System.Drawing.Bitmap bitmap, ref UIntPtr pointer, ref System.Windows.Forms.TextBox memControl, 
            System.Windows.Forms.NumericUpDown xresControl, System.Windows.Forms.NumericUpDown yresControl)
        {
            imageSize = (uint)xresControl.Value * bytesPP * (uint)yresControl.Value;
            readSize = imageSize;
            if (imgOp == ImgOP.F16_TO_I8)
            {
                tempHalfToFloatMemory = new float[imageSize];
                readSize *= 2;
            }
            else if (imgOp == ImgOP.F32_TO_I8)
            {
                readSize *= 4;
            }
            unmanagedMemory = new UnmanagedMemWrapper(readSize);
 
            bitmap = new System.Drawing.Bitmap(
                (int)xresControl.Value, (int)yresControl.Value, (int)(xresControl.Value * bytesPP), format, unmanagedMemory.ptr
            );
 
            UInt64 pointerInt = 0;
            if (memControl.Text.StartsWith("0x"))
            {
                try
                {
                    pointerInt = Convert.ToUInt64(memControl.Text.Substring(2), 16);
                }
                catch (System.Exception) { memControl.Text = "Can't parse ptr"; }
            }
            else if (!UInt64.TryParse(memControl.Text, out pointerInt))
            {
                memControl.Text = "Can't parse ptr";
            }
            pointer = new UIntPtr(pointerInt);
        }
    }
}

Tuesday, May 7, 2013

Hey! I was still using that

I'm working on a lot of things and this blog has been a bit paying the price, I'll write something serious "soon", but for now, you get this...

Don't install KB2670838. I guess everybody knows, it breaks the old Pix for Windows, and you won't even be able to replay old captures with it. True, we have a new Pix now (graphics debugger) in VS2012 and the old one was well... old. But, I don't care, and I bet not many people do, as the new debugger, for now, is much slower than the old one and more verbose, two very bad things when you have to analyze thousands of draw calls. 

Moreover, and this is the point, even if it was equally capable and just "different", that's enough not to kill the old one. People are resistant to change, change alone has a negative impact, see how much turmoil there is for each Facebook update (or iOS firmware update... all of which seem to drain your battery more, if you look at the forums...). So you'd better have a pretty good reason for it.

 I don't care about learning a new tool if the old one worked as well or in this case, better. I don't care either, as a user, about the perfectly reasonable motivations you had to invest in this new one.

Now, this is an example (I could have written the same about Apple and the Maps debacle, I didn't update there either), and I'm sure Microsoft doesn't really care much about PC/Dx11 anymore, and it's not making a ton of money on that... People are even going back to OpenGL these days.

Intel is the only company right now that seem to strongly invest in PC graphics, with tools, R&D, demos, lots of activity... GPA is the best debugger today, but I still like Pix, especially on DX10/11 is faster, and navigating it works still better than GPA's erp selection stuff.

P.S. If you're using VS2012 and you want to capture with Pix for Windows, remember you have to switch your libraries from the Window 8 SDK back to the June 2010 DX SDK ones.

Monday, April 1, 2013

Space Marine did it first!

You know, I don't usually post links to news or so, but all the guys behind Space Marine worked so hard and were so amazing I have to do this shameless plug. I think you get more attached to a product when people work really their ass off, and they are super smart, and in the end, sales are not great... Oh well...

Nowadays a few people are doing "medium range" ambient occlusion using top-down projected and blurred depth buffers. No one credited Space Marine and I think very honestly, as we didn't publish much at all on it. Still, it might be worth a second look at the slides I've pushed, as SM's technique still has a few tricks that I didn't see in the others I've seen around so far, with titling to keep the update times small and depth peeling to handle interiors and areas with multiple heights.

Shadowmaps and cascades rant/thoughts...
On a only slightly partially related note, and to add some "novel" content to this post, I was wondering for a bit about shadowmaps. We tried a couple of ways couple of ways of caching them, but in SM they failed.
Simply updating some cascades every other frame didn't work with self-occlusions of dynamic objects, and re-rendering dynamics (and more advanced methods) failed because the bandwidth required to move shadowmaps around was huge on 360/ps3.

What I don't remember anymore is if we tried to solve the problem of the self-shadowing by accessing the cascades of the dynamic objects using the position they had at the previous frame (when the cascade was computed). My memory is very bad (that's partially why I keep this blog...), I'll have to ask my then coworkers about this. If we didn't try, I was dumb. If we did, I wonder why it failed. Food for thought, maybe I'll post an update on this later on. As far as I gathered, Crytek didn't do this in their every other frame update on Crysis 2.

Update: I see the catch. Space Marine did "splat" the shadows in screen space, for good reasons. And if you do so, you reconstruct the position of the objects to be shadowed using the current frame depth buffer from a depth prepass (in our case, from the GBuffer pass, being a deferred renderer), there is no easy way to implement this.
There are ways, like stenciling and using a MRT containing last frame's world position... which could have been later used for motion blur vectors (which we did compute), so it's not crazy even in that scenario, but I'm quite sure now we didn't try all this, for how bad my memory is I would have remembered such a large change :)

Bonus hint: always point your SSAO towards the sky...