How is the back buffer flipping going? Why doesn't the pointer to the surface change?

Started by
8 comments, last by MJP 3 years, 6 months ago

Hello.

Can someone explain to me one point related to the back buffers?

Let's say we have this code on DirectX 9:

while(true) // render loop
{
	IDirect3DSurface9* backBuffer0 = nullptr, backBuffer1 = nullptr;

	device->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &backBuffer0); // let's say it returned 0xFFFFFFFF
	device->GetBackBuffer(0, 1, D3DBACKBUFFER_TYPE_MONO, &backBuffer1); // let's say it returned 0xCCCCCCCC

	std::cout << "first: " << backBuffer0 << " second: " << backBuffer1 << std::endl;

	device->Present(0, 0, 0, 0);
	
	backBuffer0->Release();
	backBuffer1->Release();
}

The output in the console is as follows:

first: 0xFFFFFFFF second: 0xCCCCCCCC
first: 0xFFFFFFFF second: 0xCCCCCCCC
first: 0xFFFFFFFF second: 0xCCCCCCCC
etc…

Why didn't the pointers change places? The buffers should have been flip.

I expected to see an output like this:

first: 0xFFFFFFFF second: 0xCCCCCCCC
first: 0xCCCCCCCC second: firstbufferptr
first: firstbufferptr second: 0xFFFFFFFF
etc…

But that didn't happen. Why?

I'll have two number 9s, a number 9 large, a number 6 with extra dip, a number 7, two number 45s, one with cheese, and a large soda.

Advertisement

It wouldn't make sense for those pointers to change, because it would imply the Present call could somehow magically change your local variables, which isn't usually possible in C++.

What normally happens in situations like this is that the meaning of the buffers is being changed internally, and DirectX changes which buffers it will be reading from and displaying next.

That's what I thought at first. But there is one thing I couldn't understand. GetBackBuffer with buffer index 0 should return the buffer that is currently being rendering to. But if each GetBackBuffer frame returns the same pointer to the surface as the surface in the previous frame, how is it currently displayed on the screen?

According to my guesses, the pointer to this surface is just a wrapper for accessing buffers that are flipping in DirectX core when Present called.

In any case, thank you for the information.

I'll have two number 9s, a number 9 large, a number 6 with extra dip, a number 7, two number 45s, one with cheese, and a large soda.

Backbuffers are never displayed on-screen - that is the front buffer. DX9 doesn't give you direct front buffer access, I believe.

But doesn't the back buffer become the front buffer when flipping? I thought they just go around in a circle and the back becomes the front and the front becomes the back.

(some new information for you https://docs.microsoft.com/en-us/windows/win32/api/d3d9/nf-d3d9-idirect3ddevice9-getfrontbufferdata)

I'll have two number 9s, a number 9 large, a number 6 with extra dip, a number 7, two number 45s, one with cheese, and a large soda.

the best way to understand it is that

GetBackBuffer( ) returns the backbuffer which was set at that location, it doesn't return the back buffer which Present( ) has switched to.

Yes back buffers and the front buffer pointers are switched in a circular buffer manner whenever you call Present( ), here is an analogous bit of pseudo DX9 code that i've just knocked up for your enlightenement ?

//pseudo
const int num = 3
byte* buffers[num]
byte* fb = buffers[0]  // lets say fb is aaaaaaa
byte* b1 = buffers[1] // lets say b1 is fffffff
byte* b2 = buffers[2] // lets say b2 is ccccccc
int n = 0

map<int, byte*> my_bbuffers

void init_back_buffer(slot, buf)
{
 my_bbuffers[slot] = buf
}

byte* get_back_buffer(slot)
{
 return my_buffers[slot]
}

byte* get_front_buffer()
{ 
  byte* new_buffer = new byte[ .... ]
 // copy contents of fb (depending on u called get_fron_buffer, 
 // this is a copy of what is fb is currently pointing to)
  memcpy(new_buffer, fb, ...) 
  return new_buffer 
}

//  if n == 0 then fb_idx = 0, b1_idx = 1, b2_idx = 2
//  if n == 1 then fb_idx = 1, b1_idx = 2, b2_idx = 0
//  if n == 2 then fb_idx = 2, b1_idx = 0, b2_idx = 1
// etc...
// present will switch pointers everytime it is called
present()
{
	n++
 	fb_idx = (n + 0) % num
 	b1_idx = (n + 1) % num
 	b2_idx = (n + 2) % num
 	
  fb = buffers[fb_idx]  // fb could now be fffffff
  b1 = buffers[b1_idx]  // b1 would then be ccccccc
  b2 = buffers[b2_idx]  // b2 would then be aaaaaaa
  
  API will now do its magic to display fffffff contents onscreen
}

main()
{
  init_back_buffer(0, b1) // this is when b1 is ffffff
  init_back_buffer(1, b2) // this is when b2 is cccccc
  loop
  {
  	get_back_buffer(0) // will always return fffffff 
  	get_back_buffer(1) // will always return ccccccc
  	
  	present() // switch pointers
  }
}

so u see, even though b1 and b2 are changing in Present ( ), the internal map remembers which one was set on each back buffer location. That is why it doesn't change when u call GetBackBuffer( )

Also do not confuse “flip”'s meaning in the olden days which literally changed the Video Graphic Memory Addresses which were used to display graphics on, for example VGA address 0xA000 would be flipped with 0xA100 to show what is on this new memory address.

DirectX 9 uses the word “flip” to mean present / show and not as flip Video Graphic Addresses etc…

Hope this helps a bit ?

Have fun ?

I still have questions. I rewrite your code to see everything clearly in the console.

#include <iostream>

constexpr int num = 3;

void* buffers[num]{ (void*)0xaaaaaaaa, (void*)0xffffffff, (void*)0xcccccccc };

void* fb = buffers[0];  // lets say fb is aaaaaaa
void* b1 = buffers[1]; // lets say b1 is fffffff
void* b2 = buffers[2]; // lets say b2 is ccccccc

int n = 0;

void* my_buffers[num]{ };

void init_back_buffer(int slot, void* buf)
{
    my_buffers[slot] = buf;
}

void* get_back_buffer(int slot)
{
    return my_buffers[slot];
}

void present()
{
    n++;
    
    int fb_idx = (n + 0) % num;
    int b1_idx = (n + 1) % num;
    int b2_idx = (n + 2) % num;

    fb = buffers[fb_idx];
    b1 = buffers[b1_idx];
    b2 = buffers[b2_idx];
    
    std::cout << "Displayed on the screen:: " << fb << std::endl;
}

int main()
{
    init_back_buffer(0, b1);
    init_back_buffer(1, b2);
    
    while(true)
    {
        std::cout << "I'm drawing here now: " << get_back_buffer(0) << std::endl;
        std::cout << "I'll probably draw it in the next frame: " << get_back_buffer(1) << std::endl;

        present();
    }
}

Output to console:

I'm drawing here now: FFFFFFFF
I'll probably draw it in the next frame: CCCCCCCC
Displayed on the screen: FFFFFFFF
I'm drawing here now: FFFFFFFF
I'll probably draw it in the next frame: CCCCCCCC
Displayed on the screen: CCCCCCCC
I'm drawing here now: FFFFFFFF
I'll probably draw it in the next frame: CCCCCCCC
Displayed on the screen: AAAAAAAA

Microsoft's documentation (https://docs.microsoft.com/en-us/windows/win32/api/d3d9/nf-d3d9-idirect3ddevice9-getbackbuffer) says:

“A value of 0 returns the first back buffer”.

That is, this is the buffer that I need to render now (SetRenderTarget(0, firstBackBuffer)) so that when I call Present(), the buffer is displayed on the screen.

But if you look closely at the output in the console that gave your code, we see that I draw constantly in the same buffer (0xFFFFFFFF), when a completely different one is displayed on the screen.

A little off.

I'll have two number 9s, a number 9 large, a number 6 with extra dip, a number 7, two number 45s, one with cheese, and a large soda.

ah lovely, it runs -lol- i wrote it from the top of my head untested ?

nevertheless, the so-called “switch” of pointers is not an actual pointer switch per say ?; in this code above i was only showing u the principle of such a switch:

In reality, the switch is, as far as I remember, a DMA controlled memory-to-memory buffer copy (so the CPU can initiate it, but will not actually do the copy), this can take place, for example, during the monitor's vertical retrace event (vsync). This means that the graphics card can also initiate the copy and run the DMA copy when it is told to by this ^^^^ algorithm about what to copy, from where and where to. So the actual memory addresses never change.

For the DMA controller to do the copying, it needs those memory addresses to remain set, so when we say that the backbuffer2 is now the FB, we're saying that the DMA controller will copy from bbuffer2 contents from its memory address to fbuffer's memory address space., etc… so the data at those locations WILL change, but not the memory addresses themselves ?

Today it gets even more complicated -lol- with modern cards, there's all sorts of controllers, they may have more than just DMA controller, RDMA, GPUDirect or whatnot … but the long shot of it all is that at the application level your memory addresses are unchanged, but under the bonnet (in the API, or in the driver or on the card itself) all these memory locations interchange data via these algorithms and controllers.

And when vsync is not enabled, the story changes slightly, but I leave it as an exercise for u to work out ?

Anyway, I hope this clarifies it a bit more… .

Don't try to fix this example without access to some DMA ?

That's it … all the best ?

That's just how D3D worked for all versions through D3D11: the cycling through buffers in the swap chain was mostly hidden from you, and your surface/texture/view pointers didn't really correspond to a single buffer in that chain.

This changed D3D12 with the FLIP model for swap chains: you actually have to manage your descriptors for the textures in the swap chain, and make sure that you render to the correct one according to the value of IDXGISwapChain3::GetCurrentBackBufferIndex. That API behaves more like you were expected D3D9 to work, where the “flipping” of swap chain textures is directly exposed to you, and your app needs to manage things accordingly.

This topic is closed to new replies.

Advertisement