DSA(直接写屏)和双缓冲

来源:互联网 发布:数据库中的sequence 编辑:程序博客网 时间:2024/06/05 00:57

DSA(直接写屏)和双缓冲

 

http://blog.chinaunix.net/u3/103999/showart_2063360.html

 

1.DSA(直接写屏)
 
1.1介绍
    使用GDI在屏幕上描画需要一个上下文转换,这会减慢描画速度。为了绕过繁琐的上下文转换,可以直接访问屏幕。这被称作直接屏幕访问。

    直接写屏就是得到屏幕的显存地址,从而直接对显存进行操作,使用直接写屏是为了加快显示速度。

    直接写屏一般会在游戏和视频中被用到。
 

1.2实现方式
    在Symbian OS中有三种方法来直接在屏幕上描画。
   
    1> CFbsScreenDevice
    CFbsScreenDevice是一个可以被发送到屏幕驱动程序SCDV.DLL的图形设备。在创建一个CFbsBitGc图形上下文之后,它能像任何其他的图形设备一样使用。然而,可以直接在屏幕上描画,而不需要使用窗口服务器。

    以下是class CFbsScreenDevice的描述:
    A graphics device interface that provides direct access to the screen, without the mediation of the window server.
    The interface adds sprite support to the CFbsDevice base class.

    示例代码:

#include <FBS.H> // fbscli.lib
#include <BITSTD.H>
// bitgdi.lib

// User::LeaveIfError(RFbsSession::Connect());

CWsScreenDevice* wsScreenDev = CEikonEnv::Static()->ScreenDevice()
;

CFbsScreenDevice* screenDev = CFbsScreenDevice::NewL(KNullDesC, 
        wsScreenDev->DisplayMode())
;

CFbsBitGc * gc = NULL;
User::LeaveIfError(screenDev->CreateContext( gc ))
;

wsScreenDev->SetAutoUpdate(ETrue);
// Sets or unsets auto-update for the screen.

gc->DrawRect(TRect(TPoint (10, 10), TSize (60, 60)) ); // draw a rect by DSA
/* if not call SetAutoUpdate(ETrue) before, it must call Update() manually to update screen. */
// screenDev->Update();


delete gc; gc = NULL;
delete screenDev; screenDev = NULL
;

// RFbsSession::Disconnect();


   
    2> 直接操作屏幕内存地址
    直接在屏幕上描画的另一种方法是从系统中查询屏幕内存地址,这可以使用 UserSrv::ScreenInfo 方法来实现。
    屏幕内存有一个32字节的头。
    即使在屏幕内存内写数据比CFbsScreenDevice稍微快一点,但是功能可能根据硬件和屏幕的设备驱动程序的不同而有差异。在一些基于Symbian OS的终端中,屏幕在内存变化的时候自动从屏幕内存中更新,而在其他的终端中描画需要明确的激活。屏幕内存地址只对目标硬件有效,因此描画代码需要分为硬件和模拟器两部分。在模拟器环境中,可以描画到一个屏外位图中,而不是屏幕内存中,然后使用正常的窗口服务器描画方法位块传送到屏幕上。环境可以通过使用__WINS__条件宏定义来检测出来。

#ifdef __WINS__ // Emulator environment
// Draw to an off-screen bitmap
#else
// Hardware environment
// Draw directly to the screen memory
#endif

   
    示例代码:

#include <e32event.h>
#include <e32svr.h>

TUint16 * screenDataAddr = NULL;

#if defined(__WINS__)
CWsBitmap * screenBufBmp = new (ELeave) CWsBitmap(
        CCoeEnv::Static()->WsSession() );
CleanupStack::PushL(screenBufBmp);

TSize size = CCoeEnv::Static()->ScreenDevice()->SizeInPixels();
User::LeaveIfError(screenBufBmp->Create(size,
        CCoeEnv::Static()->ScreenDevice()->DisplayMode() ));

screenBufBmp->LockHeap();
screenDataAddr = (TUint16*)screenBufBmp->DataAddress();

#
else
// device
TPckgBuf<TScreenInfoV01> screenInfoPckg;
UserSvr::ScreenInfo(screenInfoPckg);

TScreenInfoV01& screenInfo = screenInfoPckg();
if(screenInfo.iScreenAddressValid)
    {
    screenDataAddr = (TUint16*)screenInfo.iScreenAddress;
    
    
/* skip the palette data in the beginning of frame buffer.
     * series 60 devices have 32 Bytes palette at the beginning.
     * But be careful: UIQ devices haven't.
     */

    screenDataAddr += 16;
    }
#endif
// __WINS__

/* DSA.
 * when you copy bitmap data directly to video memory, it is essential to ensure
 * your screen mode is same as bitmap mode!
 */
if(screenDataAddr)

    {
    Mem::Copy((void *)screenDataAddr, (void *)iOffScreenBmp->DataAddress(),
        bmpLenInBytes);

    }

#if defined(__WINS__)
screenBufBmp->UnlockHeap();

.
.. // bitblt the screenBufBmp to Screen

CleanupStack::PopAndDestroy(screenBufBmp);

#
else // device
/**Once when the drawing is completed, the screen should be updated such that
 * the changes made to the screen memory will be reflected on the screen.
 * The general way of doing this is by adding a redraw event(TRawEvent::ERedraw)
 * to the system queue using UserSvr::AddEvent() API, which updates the screen
 * immediately.
 *
 * But the TRawEvent::ERedraw is a event generated by the host OS ( typically by
 * WM_PAINT ) which is meant for emulator environment( as per the S60 SDK help
 * document ). However on S60 2nd edition devices, the screen gets updated
 * properly by adding this event to the system queue. But S60 3rd Edition
 * devices does not have any immediate effect after the Redraw event is added to
 * the system queue. The screen is updated only after notifying the screen
 * device about the out of date region(s), which is a known issue.
 *
 * Also accessing the display memory with UserSvr::ScreenInfo() is deprecated
 * from S60 3rd Edition onwards.
 */

TRawEvent redraw;
redraw.Set(TRawEvent::ERedraw);
UserSvr::AddEvent(redraw)
;

/**As a workaround solution, it is still possible to use the old drawing method
 * and force the screen to update itself by specifying the out of date region
 * using CFbsScreenDevice's Update() API. The code snippet for doing the same is
 * as follows:
 */

CFbsScreenDevice* iMyScreenDev = CFbsScreenDevice::NewL(0 ,displayMode);
// the screennumber will be 0 if phone supports single screen where as the
// displaymode can be as per your choice

RRegion iMyregion;
iMyregion.AddRect(TRect(0,0,240,320));
// the out of date rect region.
iMyScreenDev->Update(iMyregion);
iMyregion.Close();

#endif



    注意:以上两种直接描画方法的一个共同的问题是窗口服务器不了解描画,因此它不能通知应用程序是否出现另一个窗口或者窗口组。 即使当应用程序得到一个事件而失去焦点的时候,它们也不能停止直接描画,因为直接描画实在太快了,并且屏幕内容有可能被弄乱。这可能发生在当在玩游戏的时候,突然有电话打进来的情况下。
   
    3> CDirectScreenAccess
    新近的GT 6.1版本提供了一个应用编程接口用于直接描画,将能解决前面提到的问题。 
    这个应用编程接口由两个类组成:一个MDirectScreenAccess类,提供用于应用程序的回调方法,还有一个CDirectScreenAccess类处理与窗口服务器的通讯。
    CDirectScreenAccess的NewL方法获得一个窗口服务器会话、CONE的图形设备、应用程序窗口和一个到MDirectedScreenAccess导出类的指针作为参数。 在CDirectScreenAccess::StartL被调用来激活直接描画支持之前,客户端窗口服务器缓冲应该溢出。为了能自动更新屏幕,屏幕设备的SetAutoUpdate方法需要使用ETrue参数(CFbsScreenDevice::SetAutoUpdate(ETrue) ),否则gc的draw指令不会在模拟器中立即显示,而是在下一次冲刷(Flush)视窗服务器时才显示。当直接描画支持激活的时候,CDirectScreenAccess产生一个CFbsBitGc图形上下文,可以被应用程序用来在屏幕上绘画。
    当另一个窗口出现在应用程序窗口上时,CDirectScreenAccess从窗口服务器取得一个事件来中断描画。 CDirectScreenAccess然后调用MDirectScreenAccess派生类的AbortNow方法,这个方法必须被应用程序重载以便中断描画。为了防止屏幕被弄乱,窗口服务器直到中断描画事件被处理的时候才画重叠窗口(回调MDirectScreenAccess::Restart)。
    
    以下是class CDirectScreenAccess的描述:
    An active object used to start direct screen access.
Direct screen access is a way of drawing to the screen without using the window server. As this avoids client-server communication, it is much faster, and may be useful for games and video. Note that some interaction with the window server is needed in order to prevent the application from drawing over other application's data.
    The object's (private) RunL() function is called by the window server in order to abort direct screen access. This might occur when another window needs to be displayed in front or when the window with direct screen access is moved. The active object's priority is RDirectScreenAccess::EPriorityVeryHigh so that direct screen access will be aborted as quickly as possible.
 
示例代码

头文件

#include <W32STD.H> // ws32.lib
    
class CMyDsaAppContainer: public CCoeControl,
        public MDirectScreenAccess
    {
// new functions ++
    ...
// other functions
public:
    virtual ~CMyDsaAppContainer();
    void Repaint(const TRect & aRect);
    
protected:
    void ConstructL(const CCoeControl* aParent, const TRect& aRect);

private
:
    void PaintUsingDsa(CBitmapContext & aGc, const TRect & aRect);
// new functions --

// from MDirectScreenAccess ++
public:
    
    
/** This function is called by the window server when direct screen access must
     * stop (for example because a dialogue is moved in front of the area where direct
     * screen access is taking place).
     * In response to this, direct screen access must stop immediately. In simple cases,
     * this will involve cancelling the active object that is driving the drawing to the
     * screen.
     * No attempt to call a Window Server API function can be made from
     * AbortNow(), because then a temporary deadlock will occur. This is because WSERV
     * is waiting to receive the client's acknowledgment that it has aborted, and so will
     * not be able to service the call. As soon as the restriction no longer applies,
     * the function Restart() will be called.
     *
     * @param aReason The reason why direct screen access was terminated.
     */

    void AbortNow(RDirectScreenAccess::TTerminationReasons aReason);
    
    
/** This function is called by the window server as soon as direct screen access
     * can resume.
     * This function should call CDirectScreenAccess::StartL() within a trap harness.
     * If this leaves, e.g. through lack of memory, direct screen access cannot be
     * restarted. StartL() re-calculates the clipping region, so that if direct screen
     * access was aborted because another window appeared in front of it, that window
     * will not be overwritten when direct screen access resumes.
     * In this function, you can resume calls to Window Server Client Side API functions.
     *
     * @param aReason Provides the reason why direct screen access was terminated.
     */

    void Restart(RDirectScreenAccess::TTerminationReasons aReason);
// from MDirectScreenAccess --
    
    ...
// other functions
    
private:
    CDirectScreenAccess * iDsa;
    ... // other member data
    };


源文件

CMyDsaAppContainer::~CMyDsaAppContainer()
    {
    ...
    iDsa->Cancel();
    delete iDsa;
    ...
    }

void CMyDsaAppContainer::Repaint(const TRect & /*aRect*/)
    {
    TRect rect = Rect();
    PaintUsingDsa(*iDsa->Gc(), rect); // drawing with DSA
    iDsa->ScreenDevice()->Update();
    }

void CMyDsaAppContainer::ConstructL(const CCoeControl* aParent,
        const TRect& aRect)
    {
    ...
    iDsa = CDirectScreenAccess::NewL(CEikonEnv::Static()->WsSession(),
            *(CEikonEnv::Static()->ScreenDevice()), Window(), *this);
    iDsa->StartL();
    ...
    }

void CMyDsaAppContainer::PaintUsingDsa(CBitmapContext & aGc,
        const TRect & aRect)
    {
    // draw with aGc
    aGc.DrawLine(...);
    ...
    }

void CMyDsaAppContainer::AbortNow(
        RDirectScreenAccess::TTerminationReasons aReason)
    {
    ...
    iDsa->Cancel();
    ...
    }

void CMyDsaAppContainer::Restart(
        RDirectScreenAccess::TTerminationReasons aReason)
    {
    TRect rect = Rect();
    ...
    iDsa->StartL();
    Repaint(rect);
    ...
    }

参考资料:
1) Implementing Direct Screen Access.pdf
 

//////////////////////////////////////////////////////////////////////


2.双缓冲

2.1介绍
当图片直接绘制到屏幕时,产生动画时。屏幕可能会有抖动闪屏。
常用的一个方法是将图片绘制在off-screen缓冲上,然后在绘制完成后拷贝它的内容到屏幕上。

在绘图量上,双缓冲并不比直接画到屏幕上小,但就显示接口的瓶颈来说,双缓冲比直接调用系统设备绘图要小。使用双缓冲的一个主要目的就是为了减小显示的瓶颈问题,从而达到图像不闪烁。如果频繁使用多缓冲,而不加以组织和限制的话,那么图像还是要闪烁的,而瓶颈问题还是存在的。绘图相对于CPU来说还是很慢的,所以要使用双缓冲。

直接往屏幕上画图的速度是很慢的。这个问题在制作动画的时候尤其明显,因为我们可能要在一秒中之内刷新屏幕几十次。如果按每秒二十五屏的速度来刷新的话,就意味着我们要在一秒中之内画一千多个小方块!
而Double Buffering的办法可以解决这个问题。这是因为:
1、写内存远比写屏(IO)快多了
2、无论所画图形有多复杂,我们只需要做一次IO操作

Symbian中的图形设备:
图形设备描述CGraphicsDevice图形设备的基类CBitmapDevice位图化图形设备的基类CFbsDevice使用字体位图服务器的图形设备基类CPrinterDevice具有打印功能的设备基类CWsScreenDevice使用窗口服务器的屏幕设备CFbsBitmapDevice使用字体位图服务器的设备具体实现CFbsScreenDevice使用直接屏幕访问(DSA),而不通过窗口服务器

Symbian中的绘图上下文的关系:


其中CWindowsGc用于屏幕绘制,CFbsBitGc则用于内存绘制。

只要能配合显示设备的速度,抵消显示瓶颈问题,就不会引起屏幕闪烁,双缓冲只是一个比较好的技术办法而已。当在运动物体动态模糊处理上,可能使用的就不是双缓冲了,而是多缓冲并存了,不过目前Symbian游戏中好像还没有用到这个技术,也许是这个技术对CPU要求过高的原因。


2.2实现方式

#include <W32STD.H> // ws32.lib
    
// protected
void CMyOffScreenPainter::ConstructL()
    {
    iOffScreenBmp = new (ELeave) CWsBitmap(CEikonEnv::Static()->WsSession());
    
    CWsScreenDevice* screenDev = CEikonEnv::Static()->ScreenDevice();
    
    User::LeaveIfError(iOffScreenBmp->Create(screenDev->SizeInPixels(),
            screenDev->DisplayMode()));
    
    iOffScreenDev = CFbsBitmapDevice::NewL(iOffScreenBmp);
    
    
// iOffScreenGc is an instance of CFbsBitGc
    User::LeaveIfError(iOffScreenDev->CreateContext(iOffScreenGc));
    ...
// other code
    }

void CMyOffScreenPainter::SizeChanged()
    {
    TRect rect = Rect();
    User::LeaveIfError(iOffScreenBmp->Resize( rect.Size() ));
    User::LeaveIfError(iOffScreenDev->Resize( rect.Size() ));
    iOffScreenGc->Resized();
    ...
// other code
    }

void CMyOffScreenPainter::DrawWithOffScreenBmp()
    {
    iOffScreenGc->DrawArc(...);
    ...
// some code about drawing
    }

void CMyOffScreenPainter::Draw(const TRect & /*aRect*/)
    {
    CWindowGc & gc = SystemGc();
    
    
// draw the offscreen bmp to srcreen
    gc.BitBlt( Position(), iOffScreenBmp);
    ...
// other code
    }

CMyOffScreenPainter::~CMyOffScreenPainter()
    {
    delete iOffScreenGc; iOffScreenGc = NULL;
    delete iOffScreenDev; iOffScreenDev = NULL;
    delete iOffScreenBmp; iOffScreenBmp = NULL;
    ...
// other code
    }

原创粉丝点击