Created
August 21, 2023 02:56
-
-
Save darksylinc/7e7edd98e467d3241cb52d492180f3e9 to your computer and use it in GitHub Desktop.
Triple Buffer vs Double buffer simulator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <assert.h> | |
#include <stddef.h> | |
#include <stdint.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <time.h> | |
#include <algorithm> | |
#include <deque> | |
// Change these parameters, recompile and run to see | |
// the results | |
// PARAMETER BEGIN | |
static const size_t kNumBuffers = 2u; | |
static const size_t kNumSwapchains = 3u; | |
static const size_t kVBlank = 16u; | |
static const size_t kCpuTime = 7u; | |
static const size_t kGpuTime = 17u; | |
static const size_t kCpuFrameVariance = 2u; | |
static const size_t kGpuFrameVariance = 2u; | |
// PARAMETER END | |
/// Returns value in range [0; bound] | |
static uint32_t bounded_rand( uint32_t bound ) | |
{ | |
uint32_t threshold = -bound % bound; | |
for( ;; ) | |
{ | |
uint32_t r = (uint32_t)rand(); | |
if( r >= threshold ) | |
return r % bound; | |
} | |
} | |
static size_t calculateFrameTime( size_t _timeToTake, const size_t variance ) | |
{ | |
int32_t timeToTake = int32_t( _timeToTake ); | |
const int32_t randomVariance = | |
int32_t( bounded_rand( uint32_t( variance * 2u ) ) ) - int32_t( variance ); | |
timeToTake += randomVariance; | |
timeToTake = std::max<int32_t>( timeToTake, 0 ); | |
return (size_t)timeToTake; | |
} | |
struct SubmittedToGpuWork | |
{ | |
size_t bufferIdx; | |
size_t cpuTimeStart; | |
size_t timeToTake; | |
size_t tickSinceLast; | |
size_t getCpuFinishedWork() const { return cpuTimeStart + timeToTake; } | |
}; | |
struct SubmittedSwapchain | |
{ | |
SubmittedToGpuWork cpuSubmission; | |
size_t gpuTimeStart; | |
size_t timeToTake; | |
size_t swapchainIdx; | |
size_t getFinishedWork() const { return gpuTimeStart + timeToTake; } | |
}; | |
static SubmittedSwapchain lockedSwapchain; | |
static size_t cpuTicksBusy = 0u; | |
static bool bFence[kNumBuffers]; | |
static std::deque<SubmittedToGpuWork> submittedToGpuWork; | |
static bool bGpuWorking = false; | |
static SubmittedSwapchain workInProgress; | |
static std::deque<SubmittedSwapchain> finishedWork; | |
static std::deque<size_t> availableSwapchains; | |
int main() | |
{ | |
srand( 101 ); // Deterministic output | |
const size_t numTicks = 1000u; | |
size_t currFrameIdx = 0u; | |
lockedSwapchain.cpuSubmission.cpuTimeStart = 0; | |
lockedSwapchain.gpuTimeStart = 0; | |
lockedSwapchain.swapchainIdx = kNumSwapchains - 1u; | |
for( size_t i = 0u; i < kNumBuffers; ++i ) | |
bFence[i] = true; | |
for( size_t i = 0u; i < kNumSwapchains - 1u; ++i ) | |
availableSwapchains.push_back( i ); | |
size_t tickStart = 0u; | |
size_t worstLag = 0u; | |
size_t totalLag = 0u; | |
size_t hitVBlanks = 0u; | |
size_t missedVBlanks = 0u; | |
double totalFps = 0; | |
for( size_t i = 0u; i < numTicks; ++i ) | |
{ | |
if( i != 0u && ( i % kVBlank ) == 0u ) | |
{ | |
// Time to present | |
bool bBlankHit = false; | |
if( !finishedWork.empty() ) | |
{ | |
const SubmittedSwapchain &work = finishedWork.front(); | |
if( i >= work.getFinishedWork() ) | |
{ | |
availableSwapchains.push_back( lockedSwapchain.swapchainIdx ); | |
assert( availableSwapchains.size() <= kNumSwapchains - 1u ); | |
lockedSwapchain = work; | |
finishedWork.pop_front(); | |
bBlankHit = true; | |
const size_t lag = i - lockedSwapchain.cpuSubmission.cpuTimeStart; | |
printf( | |
"FRAME PRESENTED! t = %i; timeStart = %i; worst_case_lag = %i; mspf = %i; " | |
" fps = %.2f\n", | |
(int)i, (int)lockedSwapchain.cpuSubmission.cpuTimeStart, (int)lag, | |
(int)lockedSwapchain.cpuSubmission.tickSinceLast, | |
1000.0f / (float)lockedSwapchain.cpuSubmission.tickSinceLast ); | |
worstLag = std::max( worstLag, lag ); | |
totalLag += lag; | |
if( hitVBlanks >= 3u ) | |
totalFps += 1000.0 / (double)lockedSwapchain.cpuSubmission.tickSinceLast; | |
++hitVBlanks; | |
} | |
} | |
if( !bBlankHit ) | |
{ | |
printf( "VBLANK MISSED! t = %i\n", (int)i ); | |
++missedVBlanks; | |
} | |
} | |
if( bFence[currFrameIdx] && cpuTicksBusy == 0u ) | |
{ | |
// Start CPU work | |
SubmittedToGpuWork work; | |
work.bufferIdx = currFrameIdx; | |
work.cpuTimeStart = i; | |
work.timeToTake = calculateFrameTime( kCpuTime, kCpuFrameVariance ); | |
work.tickSinceLast = std::max<size_t>( i - tickStart, 1u ); | |
tickStart = i; | |
cpuTicksBusy = kCpuTime; | |
submittedToGpuWork.push_back( work ); | |
bFence[currFrameIdx] = false; | |
assert( submittedToGpuWork.size() <= kNumBuffers ); | |
currFrameIdx = ( currFrameIdx + 1u ) % kNumBuffers; | |
} | |
if( cpuTicksBusy > 0u ) | |
--cpuTicksBusy; | |
if( !submittedToGpuWork.empty() && !availableSwapchains.empty() && !bGpuWorking ) | |
{ | |
// We can only do one GPU job per tick | |
const SubmittedToGpuWork &work = submittedToGpuWork.front(); | |
if( i >= work.getCpuFinishedWork() ) | |
{ | |
// GPU work started. | |
SubmittedSwapchain gpuWork; | |
gpuWork.swapchainIdx = availableSwapchains.front(); | |
gpuWork.cpuSubmission = work; | |
gpuWork.timeToTake = calculateFrameTime( kGpuTime, kGpuFrameVariance ); | |
gpuWork.gpuTimeStart = i; | |
workInProgress = gpuWork; | |
bGpuWorking = true; | |
availableSwapchains.pop_front(); | |
submittedToGpuWork.pop_front(); | |
} | |
} | |
if( ( i + 1u ) >= workInProgress.getFinishedWork() && bGpuWorking ) | |
{ | |
// Signal CPU it can start using this bufferIdx. | |
bFence[workInProgress.cpuSubmission.bufferIdx] = true; | |
finishedWork.push_back( workInProgress ); | |
bGpuWorking = false; | |
} | |
} | |
printf( "\nSummary:\n" ); | |
printf( "Total VBLANKs hits = %i; missed = %i\n", (int)hitVBlanks, (int)missedVBlanks ); | |
printf( "Avg FPS = %.02f\n", totalFps / double( hitVBlanks - 3u ) ); | |
printf( "Avg Lag = %.02f; Worst Lag = %i\n", (double)totalLag / double( hitVBlanks ), | |
(int)worstLag ); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment