• 游戏编程模式之双缓冲模式


    问题引入

      尽管计算机的处理能力相比过去有了极大的进步。但对于实时渲染的游戏程序,仍旧不能在一瞬间将同屏的所有物件全部加载出来。即使可以,用户的硬件条件参差不齐,若运行在性能较低的机器,用户将看到破碎断续加载的画面。

      从更底层形象地看断续加载的原因,就要了解画面是如何绘制的。计算机维护着一个帧缓冲区,游戏想要显示画面,就需要将像素颜色信息填写到帧缓冲区中。而显示设备就从帧缓冲区中读取像素信息并会知道屏幕上。这时候就会出现一个同步问题——当显示设备从帧缓冲区读取到计算机未填充的信息时,就会照成原本想要绘制的画面像素缺失,导致画面撕裂,从而表现为断续加载。

      如何解决计算填充像素能力跟不上显示器读取像素的频率的问题,双缓冲给出了一个比较好的解决方案。

    双缓冲方案

      两个人赛跑,已知其中一个速度快,一个速度慢。那么如何让那个速度跑的慢的长时间能够保证领先于速度快的人?很简单,就是让速度慢的人提前跑很长一段时间。双缓冲的原理就是,计算机维护两个帧缓冲区,当计算机填充好一个帧缓冲区后,就交给显示设备进行读取,再显示设备读取此帧缓冲区的过程中,计算机已经开始填充另一个缓冲区。这样的错开使得每一次显示设备都能获取到数据完整的帧缓冲区,也不会出现断续加载、画面撕裂的情况。

      双缓冲模式抽象出来,可以总结出它的使用范围:

    • 我们需要维护一些被逐步改变的、数据量较大的状态量
    • 这两个状态可能存在读写同步的问题(可能存在同时进行读和写操作)
    • 我们希望避免访问状态的代码能看到具体的工作过程
    • 我们希望读和写操作不要相互等待,尤其是读操作等待写操作完成

    双缓冲注意事项

    • 交换本身需要时间。在图形渲染API中,交换时间就是两个指针交换值的时间。注意这个时间,当交换时间大于等待时间,双缓冲就没有了意义。
    • 双缓冲增加了内存的使用。相比于单缓冲,双缓冲多维护了一个缓冲区。这就是用空间换时间。

    示例代码

    //帧缓冲区
    class FrameBuffer
    {
        public:
            static const int screen_width=1920;
            static const int screen_heigh=1080;
        
            void SetPixel(int x,int y,Color c)
            {
                if(x<screen_width&&y<screen_heigh)
                    pixels[x][y]=c;
            }
        
            Color GetPixel(int x,int y)
            {
                if(x<screen_width&&y<screen_heigh)
                    return pixels[x][y];
                else
                    return Color.Default;
            }
            
            void Clear()
            {
                for(int i=0;i<screen_width;i++)
                    for(int j=0;j<screen_heigh;j++)
                       pixels[i][j]=Color.White;  
            }
            
            Color* GetPixels()
            {
                return pixels;
            }
        
        private:
            Color pixels[screen_width][screen_heigh];
    }
    
    
    //渲染类
    class Render
    {
        public:
            void Draw()
            {
                FrameBuffer buffer=frames[drawCount%(FRAME_COUNT-1)];
                //填充数据
                buffer.SetPixel(0,0,Color.red);
                //...
                
                Swap();
            }
            
            void Swap()
            {
                currentFrame=drawCount%(FRAME_COUNT-1);
            }
            
            FrameBuffer GetDisplayBuffer()
            {
                return frames[currentFrame];
            }
    
        private:
            static const int FRAME_COUNT=2;
            FrameBuffer frames[FRAME_COUNT];
            int currentFrame=0;
            int drawCount=0;
    }
    
    //显示设备类
    class DisplayAdaptor
    {
        public:
            void Display()
            {
                FrameBuffer buffer=GetDisplayBuffer();
                //...
            }
    }
    
    
  • 相关阅读:
    Go的50坑:新Golang开发者要注意的陷阱、技巧和常见错误[2]
    Go的50坑:新Golang开发者要注意的陷阱、技巧和常见错误[1]
    进程和线程
    Linux 技巧:让进程在后台可靠运行的几种方法
    Linux 网络命令必知必会之 tcpdump,一份完整的抓包指南请查收!
    这些好用的 Chrome 插件,提升你的工作效率
    golang学习笔记-go mod的使用
    性能监控和分析工具---nmon
    Jenkins
    程序员画图工具总结
  • 原文地址:https://www.cnblogs.com/ZhuSenlin/p/15431836.html
Copyright © 2020-2023  润新知