How to Use
AMD Display Library
(ADL)
![]() |
Copyright © 2009 Advanced Micro Devices, Inc. All rights reserved.
Table of Contents
3.2. Threading and Concurrent Access
4.1. Include Files from the SDK to Use
4.4. Initializing ADL and providing the memory allocation function
Prior to AMD Display Library (ADL), AMD provided several SDKs (PDL, DSP, CCC, COM) for external use under Windows, but none for Linux. The goal of ADL is to provide a single SDK for both operating systems and to eventually replace the other SDKs.
The majority of the APIs in ADL wrap the company’s private APIs thus allowing the library’s client to access information related to the graphics associated with the system. ADL is used strictly for exposing “Graphics Hardware” support. Other hardware (such as CPUs, Northbridge, etc…) or software subsystems (such as Catalyst Control Center and HydraVision) will expose an interface via their own binaries, if they require one.
ADL binaries are delivered as part of the Catalyst display Driver package while the documentation, definitions and sample code are distributed via a web-download as a single ZIP package.
For Windows XP 32-bit and Windows Vista 32-bit operating systems, the ADL library binary is located in:
%WINDOWS%\System32\atiadlxx.dll
For Windows XP 64-bit and Windows Vista 64-bit, there are two binaries:
· %WINDOWS%\System32\atiadlxx.dll (Native OS driver)
· %WINDOWS%\SysWOW64\atiadlxy.dll (32-bit OS driver)
For LINUX OS, the locations and binary names are:
Linux 32-bit Runtimes (32-bit OS and 64-bit OS):
/usr/X11R6/lib/Libatiadlxx.so
Linux 64-bit Runtimes (64-bit OS):
/usr/X11R6/lib64/Libatiadlxx.so
Important: The 32-bit library in provided in 64-bit Windows OSes to ensure support for the unmanaged 32-bit applications. These applications cannot load the native 64 bit ADL library therefore after a load failure they should attempt to load the 32-bit library. Note that both binaries have different names (not only locations). This will be discussed in more detail later in this document.
ADL provides flat C-style exports that are typical for Windows DLLs and Linux SO binaries.
In general, ADL makes no assumption as to the caller’s environment. ADL creates and destroys its own memory allocations, and does not provide any explicit thread safety.
APIs that return data will either return simple data (via the stack), or will require the caller to pre-allocate the memory and provide references via the API call signature (API-specific). If the ADL calls back into the caller, it will be on the same thread and all data will be managed by the ADL; the caller is not to attempt memory management of any ADL-supplied data.
The name of a public API starts with “ADL” and typically ends with “Get”, “Set”, or “Caps”. The following is an example of the naming convention:
ADL_Xxx_Yyy_Get
ADL_Xxx_Yyy_Set
Xxx can be set to “Main”, “Adapter”, “Display”, etc., whereas Yyy describes the function in detail.
For example:
ADL_Display_NumberOfDisplays_Get is used to retrieve the number of displays supported by an adapter.
The return code is an integer describing the ADL level results. In general, negative values indicate that output parameters are not valid, zero indicates normal success, and a positive value indicates success requiring a mode change or even a system restart.
A “0: Success” result indicates that the Driver received and processed the request in a normal fashion, and that the output parameters can be processed as per each API’s specification.
The complete set of return values is described in the html documentation that is part of the SDK.
ADL makes no assumption as to the threading model of the caller, and provides no threading control within ADL. The calling process is expected to provide thread safety in that the caller must ensure that only a single thread at any given time is active within the ADL. The caller may use any number of threads to call into the ADL at different times; however, ADL does not guarantee any thread safety. While some ADL API calls may survive concurrent access from multiple threads, some may not. Rather than incur per-call overhead in ADL to ensure such thread safety, noting that many callers typically already have to deal with multiple thread access to internal data (often by using the same single thread to access its internal data and the ADL), the ADL design requires that the caller arrange for thread safety if it is needed.
In cases where multiple processes are each using their own ADL instance to access the Driver and/or OS, the Driver and OS must provide multiple process safety to the channel(s) and OS entry point(s). In Windows, the GDI layer uses a global mutex to ensure only a single call (whether from the same process or not) enters the kernel for Escape call processing. Likewise, in Linux, the X-Server also ensures such single threading for all calls made to the X-Server across all processes and from within any process. As to OS calls, the OS itself provides thread and process safety where needed—ADL does not augment this in any way.
· Sequential Program Type: The application makes one or more API calls (beyond the initialize/shutdown API calls) to perform a specific job, involving a series of steps: checking whether a needed interface is supported; checking whether a Feature is supported; retrieving capabilities of the Feature; retrieving Properties of the Feature; changing the value of the adjustable Properties; and invoking custom actions. These are typically command line driven programs (such as command line type configuration utilities or automated test utilities) performing a specific task.
· Event Driver Program Type: The application operates by making groups of calls to the ADL based on responses to pre-programmed events detected and handled in the application. The application itself may be single-threaded or multi-threaded. The main difference between event-driven and sequential programs is the overall sequence of accesses to AD. In event-driven programs, access to ADL tends to involve a small number of APIs grouped together in a sequence but a large number of APIs overall, in a group order determined by external events.
From an ADL perspective, there is no difference between types of such programs.
An application loads the ADL, and then obtains a collection of the logical adapters (corresponding to GPU controllers) with each entry containing basic information about each controller on each GPU. The most important piece of information is the AdapterIndex, which identifies a specific controller on a specific GPU. The index provides entry to a table of all knowable GPU controllers.
Using the AdapterIndex, the application then calls other ADL APIs to:
Each ADL API method signature defines appropriate in and out parameters. The caller supplies data as part of the in parameters, and the API returns data as part of the out parameters. Additionally, the API returns an ADL call status (an ADL concept value, not a Driver Component supplied value).
Most of the ADL APIs call a corresponding “pack” routine in the internal Pack and Unpack Library to create a message, and then sends the message to the Driver. The Driver then acts upon the sent message and returns a return message as a result.
Upon receipt of a return message from the Driver corresponding to the sent message, the API then calls the appropriate “unpack” routine in the internal Pack and Unpack Library to decode the returned message into the appropriate out parameters. The API then returns a result code relating to the ADL level activity. In some cases, the API may perform multiple Driver calls and/or OS calls. Calls to the OS do not generally use the Pack/Unpack mechanism, but instead use some OS-specific calling convention.
Driver Component errors, OS errors, etc., may be returned as out parameters as defined per API method.
Generally, an ADL API will take as parameters various data as defined by the header files exposed by AD. It also takes a controller-specific AdapterIndex identifying the controller of a specific GPU on which to act. The Management API will call a Pack Library packing routine to create an appropriate message using the Driver header files (callers to ADL never see or need to know the Driver header files). The Pack Library routine returns a message packed with the appropriate data for the Driver. The API then calls the so-called “Channel Library” with the AdapterIndex and the message. The Channel delivers the message and receives (synchronously inline) a response from the Driver—this is a “waiting” call and will stall the thread on which the call is made. The API then unpacks the response message by calling the appropriate Unpack Library unpack routine, extracting the data and returning the data to the application via out parameters in the Service API method. The APIs returns a result code with 0, meaning success.
Each OS has a difference scheme for identifying individual GPUs, as well as for obtaining and managing the collection of accessible GPUs. On some systems, some GPUs are not accessible by a given user due to user privileges. One of the primary principles behind the design of ADL is to program each API so it still services all OSes (as appropriate) with minimal (or no) conditional coding.
Even though ADL users do not need to know all of the internal implementation details, two internal libraries are of a particular importance: the Pack/Unpack library and the Channel library.
Prior to ADL, the applications coded their own pack/unpack logic (create C-type “structures” by stuffing them using local variables, and then validate that the values are all correct), and either directly route to the Driver or use a private entry point (like PDL) to perform the routing.
In most cases, the application data is kept in various variables/locations throughout the application (such as embedded in UI forms or in some data store), and is amalgamated at I/O time, typically within some internal per-ESC-call application method. ADL defines a single code implementation rather than having each and every program implement its own.
Further, the ADL also deals with routing the requests in an OS-agnostic fashion (which PDL cannot, since it is purposed only for Microsoft Windows). This provides more opportunity for the calling applications (including test applications) to reduce OS-specific coding. While it is true that calling applications will still need some OS-specific code to deal with OS-specific APIs, the bulk of the calling application could become OS-agnostic thereby reducing development and testing burdens—this is one of the primary principles behind ADL.
The Channel Library is a library that is internal to ADL. It provides the OS-specific implementation for routing packed messages from the ADL to the Driver and to return a message back again for unpacking.
At present, there are three defined channels:
1. Windows XP channel—Uses GDI to send ESC calls to the Driver.
2. Windows Vista channel—Uses DirectX to send ESC calls to the Driver.
3. Linux channel—Uses AMD proprietary X-Server extension to communicate with the DDX driver.
This channel concept can be extended to future OSes, such as Windows 7 if necessary. For example, discussions exists that some future Windows OS may remove the traditional ESC call routing for Graphics Drivers, perhaps moving to an IOCTL or similar functionality. ADL Channel Library is designed to allow such new channels in a seamless and transparent fashion.
On Linux, the format of packed messages for common functions follows Vista format. Some Linux-specific functions are defined separately to fit into the existing “X-extension” implementation. Most of the functions generally follow the CWDDE message mechanism.
Provided along with ADL SDK is an unmanaged C code sample application that demonstrates some of the principles on how to use ADL. This chapter will not discuss the application in detail, but will emphasize on some important points.
The three files in the include folder are the only ones necessary for your project:
adl_defines.h Contains all defined constants.
adl_structures.h Contains the definitions of all structures.
adl_sdk.h Contains the typedef of the Memory Allocation Callback function.
It is sufficient for your project to include only adl_sdk.h because it includes adl_structures.h, which includes adl_defines.h. The three files should be located in the same folder.
The following lines from the following code sample are self-explanatory. Each OS uses a corresponding function to load the library (atiadlxx.dll for Windows OSes and libatiadlxx.so for Linux).
#if defined (LINUX)
hDLL = dlopen( "libatiadlxx.so", RTLD_LAZY|RTLD_GLOBAL);
#else
hDLL = LoadLibrary("atiadlxx.dll");
if (hDLL == NULL)
// A 32 bit calling application on 64 bit OS will fail to LoadLIbrary.
// Try to load the 32 bit library (atiadlxy.dll) instead
hDLL = LoadLibrary("atiadlxy.dll");
#endif
While in Linux, the 32-bit and 64-bit libraries have the same names but are placed in different locations.
In Windows, the native library for the corresponding OS is always named atiadlxx.dll. Thus a 32-bit application on a 32-bit OS will succeed with loading atiadlxx.dll, and a 64-bit application will succeed with loading atiadlxx.dll on a 64-bit OS. The attempt of a 32-bit application to load the native 64-bit atiadlxx.dll on a 64-bit OS, however, will fail. Therefore the application should immediately attempt to load the 32-bit version of ADL (atiadlxy.dll). See the code above and the binary locations in chapter 2.
The provided example shows the steps necessary to call an ADL API:
1. For each API you plan to use, define a pointer to a function type using typedef. The typedefs may be organized as a separate include file. Here’re some typedefs from the sample:
typedef int ( *ADL_MAIN_CONTROL_CREATE )(ADL_MAIN_MALLOC_CALLBACK, int );
typedef int ( *ADL_MAIN_CONTROL_DESTROY )();
typedef int ( *ADL_ADAPTER_NUMBEROFADAPTERS_GET ) ( int* );
typedef int ( *ADL_ADAPTER_ADAPTERINFO_GET ) ( LPAdapterInfo, int );
2. Somewhere in the code, instantiate the pointers of those types. Here’s the code from the sample:
ADL_MAIN_CONTROL_CREATE ADL_Main_Control_Create;
ADL_MAIN_CONTROL_DESTROY ADL_Main_Control_Destroy;
ADL_ADAPTER_NUMBEROFADAPTERS_GET ADL_Adapter_NumberOfAdapters_Get;
ADL_ADAPTER_ADAPTERINFO_GET ADL_Adapter_AdapterInfo_Get;
The main and only initialization API for ADL is:
int ADL_Main_Control_Create ( ADL_MAIN_MALLOC_CALLBACK callback, int iEnumConnectedAdapters );
This function should be called first (the exact steps are in the next section), prior to calling any other ADL APIs. It initializes the library and provides the memory allocation function as the first parameter.
What is it for? Some APIs, such as ADL_Display_DisplayInfo_Get(), get an uninitialized pointer as an input parameter. Then they call (already provided through “Create”) the memory allocation function, which allocates memory from the user’s heap. The user is also responsible for the de-allocation of the memory. This technique is illustrated through the following example.
User may provide a memory allocation function to serve an application’s specific needs but it could also be a generic one as shown in the example. Below are the memory allocation and de-allocation functions from the sample code:
// Memory allocation function
void* __stdcall ADL_Main_Memory_Alloc ( int iSize )
{
void* lpBuffer = malloc ( iSize );
return lpBuffer;
}
// Optional Memory de-allocation function
void __stdcall ADL_Main_Memory_Free ( void** lpBuffer )
{
if ( NULL != *lpBuffer )
{
free ( *lpBuffer );
*lpBuffer = NULL;
}
}
The second parameter is iEnumConnectedAdapters. If its value is zero, the adapter information will be collected only for the adapters that are physically present and enabled in the system. If the value is non-zero, information of all adapters that have ever been present (even currently away) in the system will be collected.
The address of the desired API can be obtained by using GetProcAddress and the handle of the loaded ADL library. Since ADL_Main_Control_Create() must be the first API called, here’s how to obtain its address and call the API:
ADL_Main_Control_Create = ADL_MAIN_CONTROL_CREATE)GetProcAddress(hDLL,"ADL_Main_Control_Create");
if ( NULL != ADL_Main_Control_Create)
ADL_Err = ADL_Main_Control_Create (ADL_Main_Memory_Alloc, 1);
The return value ADL_Err is an integer and should be ADL_OK to indicate successful ADL initialization.
Calling the other APIs should follow the same pattern:
· Obtain the function’s address and check for NULL.
· Call the function with the proper parameters.
· Check the return code for errors.
In the provided sample, all pointers are obtained at the beginning and checked for NULL. If only one API is not available the application fails. It’s up to the user to decide if all function pointers will be obtained at once or one by one, when needed.
Here’s an example of how information about each adapter is obtained. In this example, the allocation of the memory for the AdapterInfo structure is done through malloc. Most of the ADL APIs allocate memory that way, while some APIs (see the last example) use the memory allocation function whose address is passed by the ADL_Main_Control_Create API. The documentation points those APIs out.
Let’s now get the number of the adapters in the system.
if ( ADL_OK != ADL_Adapter_NumberOfAdapters_Get ( &iNumberAdapters ) )
{
printf("Cannot get the number of adapters!\n");
return 0;
}
We then check if this is a non-zero value and allocate memory with the size of the AdapterInfo structure times the number of adapters. We pass the pointer to this memory to ADL_Adapter_AdapterInfoGet(). Upon the function’s return, the structure(s) are filled with data corresponding to the graphics adapters in the system. The following is the code snippet:
if ( 0 < iNumberAdapters )
{
lpAdapterInfo = malloc ( sizeof (AdapterInfo) * iNumberAdapters );
memset ( lpAdapterInfo,'\0', sizeof (AdapterInfo) * iNumberAdapters );
// Get the AdapterInfo structure for all adapters in the system
ADL_Adapter_AdapterInfo_Get (lpAdapterInfo, sizeof (AdapterInfo) * iNumberAdapters);
}
The following example is to illustrate the usage of the memory allocation function whose address was passed as a parameter when ADL_Main_Control_Create() was called. As mentioned, one such API that relies on the memory allocated on the application’s heap is ADL_Display_DisplayInfo_Get(). The following shows how to call this API:
· Create a pointer to a pointer to the ADLDisplayInfo structure. LPADLDisplayInfo is predefined by ADL, so this line (from the sample code) will do:
LPADLDisplayInfo lpAdlDisplayInfo = NULL;
· Create an integer to receive the number of displays upon the function’s return:
int iNumDisplays;
· Obtain the API’s address using GetProcAddress. Do not forget to check for NULL!
ADL_Display_DisplayInfo_Get = (ADL_DISPLAY_DISPLAYINFO_GET)GetProcAddress(hDLL,"ADL_Display_DisplayInfo_Get");
· Call the API with the proper parameters:
ADL_Err = ADL_Display_DisplayInfo_Get (iAdapterIndex, &iNumDisplays, &lpAdlDisplayInfo, iForce );
Important: Note that the lpAdlDisplayInfo pointer is not initialized. It is the ADL that calls the provided memory allocation function to allocate memory. Since this function is in the user’s application (ADL_Main_Memory_Alloc), the memory is allocated from the user’s heap, and the user should de-allocate the memory (see below for the code example).
When the function returns successfully (ADL_OK), iNumDisplays holds the number of displays, while lpAdlDisplayInfo points to an array of iNumDisplays elements of ADLDisplayInfo structures.
· In the end, do not forget to de-allocate the memory. You may use the already defined function in the example:
ADL_Main_Memory_Free ( &lpAdlDisplayInfo );
Note that the memory de-allocation helper function takes a pointer to pointer as a parameter. This allows the function to free the memory and set the pointer to NULL.
These examples and the sample code should be enough for you to start using and enjoying ADL (AMD Display Library).
Please do not forget that this is the first release. We expect your comments and suggestions in order to make this library more useful and easy to use.