Implementing Dynamic DSA Allocation

This topic describes how device creators can allocate the Direct Screen Access (DSA) buffer dynamically. This feature is new in Symbian^3 (S^3).

Target audience: Device creators.

Overview

Before the introduction of ScreenPlay, applications (such as video and EGL games) that required high frame rates used Direct Screen Access (DSA) to bypass the Window Server and draw directly to the frame buffer (video memory). However, some interaction with the Window Server was necessary in order to prevent the application from drawing over other applications' data. The frame buffer was allocated at device startup and was never freed.

In ScreenPlay, applications that require a high frame rate can render directly to composition surfaces. In ScreenPlay the Window Server's content is also rendered to a composition surface, which is known as the UI surface. This means that the Window Server and its render stages do not render directly to the frame buffer. However, DSA is still supported in order to provide backwards compatibility.

The DSA buffer is now just another buffer (known as the DSA buffer), which the Window Server process makes use of only when a client uses DSA on a visible window. This means that it is no longer necessary to allocate the DSA buffer at startup and not free it. In fact to do so in ScreenPlay is wasteful of resources.

For this reason, it is now possible to allocate the DSA buffer as a shared chunk and free it when it is not in use. This requires changes in the implementation of the LCD Driver (also known as the LCD Extension). For example, in order to free the DSA buffer when it is no longer in use, the LCD Driver must keep track of the processes that use the buffer and when they finish with it. These changes can be implemented regardless whether ScreenPlay is in use.

Note: Semi-transparent UI content over content provided by Direct Screen Access (DSA) has never been supported and ScreenPlay does not change this.

DSA client-side API changes

To use DSA, the client creates an instance of CDirectScreenAccess. This allows the client to draw directly to the DSA buffer and to determine the visible area of its window and be notified when the visible area changes. DSA is typically performed by using the CFbsScreenDevice returned by CDirectScreenAccess::ScreenDevice().

However, applications can call the HAL user-side API (described below) to allocate the DSA buffer (if not already allocated) and obtain the DSA buffer's address directly. Clients that do this can now use DSA simply in order to be notified when the visible area changes and not for accessing the DSA buffer itself. This is called region-tracking DSA. (CFbsScreenDevice also obtains the DSA address through the use of the HAL user-side API.)

To use region-tracking DSA, call the following overload of the CDirectScreenAccess::NewL() function and pass ETrue as the final argument:

CDirectScreenAccess::NewL(RWsSession&,CWsScreenDevice&,RWindowBase&,MDirectScreenAccess&,TBool)

This does not allocate a screen device and graphics context. It therefore does not cause the DSA buffer to be allocated.

Here is an example that shows how to create a CDirectScreenAccess object to track visible regions only:

CDirectScreenAccess * cDsa = NULL;
    
// Specify additional parameter to NewL to indicate region tracking only.
cDsa = CDirectScreenAccess::NewL(TheClient->iWs,*TheClient->iScreen,*win,
           *this, ETrue); 
   
// In this mode, the client cannot get a screen device or graphics 
// context from the DSA object.
CFbsScreenDevice sd = cDsa->ScreenDevice();
CFbsBitGc gc = cDsa->Gc();
    
ASSERT(sd == NULL)
ASSERT(gc == NULL)

User-side HAL API changes

The user-side HAL API is a set of static functions that get and set the hardware attributes of the device. For general information about the interface to HAL, see User-Side Hardware Abstraction Technology.

The HAL::Get() function is used to obtain information about the DSA buffer memory.

static IMPORT_C TInt Get(TAttribute aAttribute, TInt &aValue); 
static IMPORT_C TInt Get(TInt aDeviceNumber, TAttribute aAttribute, 
     TInt &aValue);

In versions earlier than S^3, in order to obtain the memory address of the DSA buffer, a client calls HAL::Get() with an attribute of HALData::EDisplayMemoryAddress. If successful, the call returns KErrNone and sets the aValue parameter to the virtual address of the DSA buffer in the caller’s process. If the DSA buffer cannot be allocated, the call returns KErrNoMemory and sets the aValue parameter to NULL.

From S^3 onwards, a client can obtain a handle to the DSA buffer by calling HAL::Get() with an attribute of HALData::EDisplayMemoryHandle. If successful, the call returns KErrNone and sets the aValue parameter to the value of an RChunk handle in the caller’s process. If the DSA buffer cannot be allocated, the call returns KErrNoMemory and sets the aValue parameter to 0.

The advantage of using a handle is twofold:

  • The memory chunk exists for as long as the handle is open.

  • The handle is automatically closed by the kernel if the process holding it exits prematurely.

When possible, legacy clients should be changed to take advantage of the new HALData::EDisplayMemoryHandle attribute. This means that you need to modify HAL::Get(EDisplayMemoryAddress) calls to use HALData::EDisplayMemoryHandle to obtain a handle to the DSA's shared chunk. You also need to add code to close this handle when the memory is no longer required.

For example, replace the following code:

err = HAL::Get(screenId, HALData::EDisplayMemoryAddress, dsaAddr);

With something like this:

err = HAL::Get(screenId, HALData::EDisplayMemoryHandle, val);
if (err == KErrNone)
    {
    RChunk chunk;
    err = chunk.SetReturnedHandle(val);
    if (err == KErrNone)
        {
        iDSAChunk = chunk;
        err = iDSAChunk.Duplicate(RThread(), EOwnerProcess);
        chunk.Close();
        if (err == KErrNone)
            {
            dsaAddr = (TInt) iDSAChunk.Base();
            }
else if (err == KErrNotSupported)
    {
    err = HAL::Get(screenId, HALData::EDisplayMemoryAddress, dsaAddr);
    }

Kernel-side HAL changes

HAL requests are handled on the kernel side by HAL handlers. A single HAL handler deals with all the HAL attributes defined in a particular HAL group. Each HAL group has a one-to-one relationship with a HAL handler. EHalGroupDisplay identifies the group associated with the HAL display attributes.

In S^3 the TDisplayHalFunction enum has two new attributes—EDisplayHalGetDisplayMemoryAddress and EDisplayHalGetDisplayMemoryHandle. These allow the generic user-side HAL code to obtain the DSA buffer's address and handle independently of all of the other display attributes.

Handling HAL::Get(EDisplayMemoryAddress)

In versions earlier than S^3, the DSA buffer address is returned from the HAL handler as part of a TVideoInfoV01 class in response to an EDisplayHalCurrentModeInfo request. The user-side HAL code then extracts the address from the TVideoInfoV01 structure and returns it.

Configuration file setting

The HAL configuration file config.hcf therefore includes the following:

EDisplayMemoryAddress=ProcessDisplayCurrentModeInfo

Modifications to generic HAL code

In S^3 the switch case for EDisplayMemoryAddress in ProcessDisplayCurrentModeInfo() is as follows:

case HAL::EDisplayMemoryAddress:
    
// If the address is zero, the actual address
// is returned by EDisplayHalGetDisplayMemoryAddress
if (info.iVideoAddress == 0)  
    {
    r = UserSvr::HalFunction(EHalGroupDisplay, 
                             EDisplayHalGetDisplayMemoryAddress, 
                             aInOut, (TAny*)EFalse, aDeviceNumber);
    }
else
    {
    *(TInt*)aInOut = info.iVideoAddress;
    }
break;

Dealing with HAL::GetAll()

HAL::GetAll() sets the input parameters passed to the HAL handler to -1. You can use this in the LCD Driver to determine whether the call for the DSA buffer's address is from HAL::GetAll() and if it is, to not allocate the memory. For example:

case EDisplayHalGetDisplayMemoryAddress:
    {
    TInt val = 0;
    TInt passedIn = 0;
    kumemget32(&passedIn, a1, sizeof(TInt));
    
    // Not from a GetAll().
    if (passedIn != -1)    
        {
        r = DisplayMemoryAddress(aDeviceNumber, val);
        }
    else
        {
        r = KErrNone;
        }
    
    // Write the value returned back to the user side.
    kumemput32(a1, &val, sizeof(TInt));
    }
    break;

Keeping track of processes that call HAL::Get(EDisplayMemoryAddress)

Legacy DSA clients request the DSA buffer address rather than the handle and assume that the address will remain valid for the lifetime of the process. The LCD Driver needs to keep track of processes that call HAL::Get(EDisplayMemoryAddress) and free the DSA buffer when the last of these processes has exited.

The LCD Driver must therefore maintain a list of the relevant processes and the address of the DSA buffer for each one. When the number of processes using a buffer address returns to zero, the buffer is freed.

To track the usage, the LCD Driver creates a new shared chunk for each process that calls HAL::Get(EDisplayMemoryAddress). The LCD Driver then calls Kern::MakeHandleAndOpen() to create a user handle to the shared chunk for the calling process. The LCD Driver closes the kernel-side handle to the chunk.

There is then a handle to the shared chunk in the user process, which means that the shared chunk stays open for as long as the process exists. When the user process exits, the kernel closes any open handles that the process owns. This causes the deletion of the shared chunk and its Deferred Function Call (DFC) to run. The DFC removes the process from the list and decrements the reference count of users of the DSA memory.

Here is the sequence when a process calls HAL::Get(EDisplayMemoryAddress) for the first time:

  1. Legacy process X implicitly calls HAL::Get(EDisplayMemoryAddress) for the first time by creating a DSA session that creates a CFbsScreenDevice.

  2. The driver looks for process X in the list of processes and addresses and does not find it.

  3. The driver creates a shared chunk for process X with a destruction DFC.

  4. The driver creates a user-side handle to the shared chunk.

  5. The driver closes its DChunk handle to the shared chunk.

  6. The driver calls TUint8* Kern::ChunkUserBase(DChunk* aChunk, DThread* aThread) to get the address of the shared chunk in the user process .

  7. The driver stores the calling process's ID and the address of the shared chunk in a table.

  8. The driver returns the address of the shared chunk to the calling process.

Here is the sequence when the same process calls HAL::Get(EDisplayMemoryAddress) again:

  1. Legacy process X implicitly calls HAL::Get(EDisplayMemoryAddress) again by creating a DSA session that creates a CFbsScreenDevice.

  2. The driver looks for the process in the list of processes and addresses.

  3. The driver returns the address previously given to the process.

Here is the sequence when the same process exits:

  1. The kernel cleans up any open handles for the exiting process.

  2. The shared chunk destruction DFC executes and removes the process from the list and decrements the DSA usage count.

  3. The DFC checks the DSA usage count and if it is zero, the DFC frees the DSA memory.

Handling HAL::Get(EDisplayMemoryHandle)

In S^3 there is a new GetDisplayMemoryHandle() function in the user-side HAL implementation. This function is the handler for the new EDisplayMemoryHandle attribute.

Configuration file setting

The following line therefore needs to be added to the config.hcf file:

EDisplayMemoryHandle=GetDisplayMemoryHandle

Dealing with HAL::GetAll()

HAL::GetAll() sets the input parameters passed to the HAL handler to -1. You can use this in the LCD Driver to determine whether the call for the DSA buffer's address handle is from HAL::GetAll() and to take appropriate action. For example:

case EDisplayHalGetDisplayMemoryHandle:
    {
    TInt val = 0;
    TInt passedIn = 0;
    kumemget32(&passedIn, a1, sizeof(TInt));
    
    // Not from a GetAll().
    if (passedIn != -1)    
        {
        r = CreateNewChunkToRepresentDSABuffer(aDeviceNumber,  val);
        }
    else
        {
        r = KErrNone;
        }

    
    kumemput32(a1, &val, sizeof(TInt));
    }
    break;

Keeping track of processes that call HAL::Get(EDisplayMemoryHandle)

New DSA clients request the DSA buffer handle rather than the address. After a process obtains the handle, the DSA buffer is in use for this process until the handle is closed or the process exits (which implicitly closes the handle).

To track the usage, the LCD Driver creates a new shared chunk every time a process calls HAL::Get(EDisplayMemoryHandle). The LCD Driver then calls Kern::MakeHandleAndOpen() to create a user handle to the shared chunk for the calling process. The LCD Driver closes the kernel-side handle to the shared chunk and returns the user-side handle to the calling process.

When the user process closes the handle, the shared chunk is deleted and its deletion DFC runs. The DFC decrements the reference count of users of the DSA memory.

Here is the sequence when a process calls HAL::Get(EDisplayMemoryHandle) for the first time:

  1. The driver creates a new shared chunk for this process with a destruction DFC.

  2. The driver creates a user-side handle to the shared chunk.

  3. The driver closes its DChunk handle to the shared chunk.

  4. The driver returns the user-side handle to the calling process.

Here is the sequence when a process closes the DSA buffer:

  1. The shared chunk destruction DFC executes and decrements the DSA usage count.

  2. The DFC checks the DSA usage count and if it is zero, the DFC frees the DSA memory.