SDL Graphics with Vulkan®

Vulkan® is a generation API for high-efficiency access to graphics and computing on modern GPUs. It is an open-standard, cross-platform API designed from the ground up by industry experts collaborating under the Khronos consortium. It aims to address the inefficiencies of existing 3D APIs, such as OpenGL®, which are designed for single-core processors and lag to map modern hardware. It provides a much lower-level fine-grained control over the GPU to maximize performance and achieve a consistent user experience across different operating environments. For general information on Vulkan®, and the comparative merits of Vulkan® and OpenGL®, see the official Khronos Vulkan® Web site and OpenGL® vs. Vulkan®.

The Tizen platform supports the Vulkan API in order to provide the most cutting-edge 3D programming tools for you to create high-quality games and real-time graphics in applications. Vulkan® is especially recommended for performance- or latency-sensitive applications. With Vulkan®, you can achieve a much smoother user experience by parallelizing the rendering job across multiple threads which can feed the GPU in an efficient manner. Applications demanding explicit control on work submission, synchronization, and less power consumption can seriously consider migrating to Vulkan® as well. Tizen allows the use of the Vulkan API through SDL.

Figure: Vulkan® in Tizen

Vulkan in Tizen

Before using the Vulkan API in Tizen, make sure that you are familiar with the following key Vulkan® features, which help you to align your application design around Vulkan® concepts and exploit the hardware to the fullest:

  • Cross-platform

    Vulkan® is designed to run on a wide range of platforms and hardware with very different form factors and power envelopes. While OpenGL® has the OpenGL® ES variant for mobile and embedded systems and OpenGL® for desktops, Vulkan® provides a unified API that is completely identical across all these platforms. You simply link to the same library, with the same header and the same code on all targeted platforms, and the application runs everywhere. By not limiting itself to a subset of platforms, Vulkan® enables you to "write once and run everywhere". This means that by targeting this API, you can achieve a large market share across both mobile and desktop devices.

  • Explicit control

    Vulkan® is an explicit API, and wants you to define "exactly what you want", rather than relying on hidden driver heuristics to do the right thing. Vulkan®, by design, is a very low-level API that provides applications direct control over the GPU. Older higher-level APIs hide most of the costly operations, such as memory management and resource synchronization, denying you any control over them.

    Vulkan® attempts to turn as much of the problematic "implicitness" of older APIs as possible into "explicit" application choices. It provides a number of explicit mechanisms for many operations, such as memory allocation, synchronization, and work submission, allowing applications to be more expressive.

  • Multi-core friendly

    Older APIs written in the single-core processor age do not do enough to take advantage of today's multi-core processors and thereby max out the single core by over-burdening the render thread with all heavy tasks, such as error checking, implicit resource tracking, synchronization, and state validation.

    Vulkan® provides tools and design choices to spread its workload across multiple threads. It achieves this by removing from the driver the thread-specific features, such as global data, access synchronization, thread safety, and order guarantee, and handing the responsibility to you. By carefully handling the threads and scaling across multiple threads on a device with more cores, you can distribute your workload across threads much better. This leads to better efficiency and better performance in applications that otherwise find themselves maxing out a single core.

  • High efficiency

    With a better design, Vulkan® offers the potential to reduce the workload of the render thread, thereby allowing the application to draw less power and generate less heat. It achieves better throughput by removing lots of (mostly) unnecessary background work, such as runtime error checking, state validation, and shader compilation, from the main loop. It delegates that background work to the tooling and layers, which are used during development and get removed before the application makes it to a consumer device.

  • Portable

    With the introduction of an intermediate shader language, called SPIR-V, Vulkan® improves the shader program portability and allows you to write shaders in languages other than GLSL (such as C/C++). This avoids the need for the compiler to execute during runtime to translate the shader code and allows offline shader precompilation. It also relieves the vendors from the shader/kernel source shipping and IP leakage.

OpenGL® vs. Vulkan®

When you consider the differences and advantages between Vulkan® and OpenGL®, Vulkan® basically complements OpenGL® by addressing specific users who want to have a quite low-level API with a much better abstraction of the modern hardware giving a lot of control, predictability, and high performance at much greater efficiency. On the other hand, OpenGL® is a much higher-level API that does many things on your behalf inside the driver with less burden on you. It continues to be the API of choice for a wide range of developers who want to have the shortest path to a functionally correct application.

When selecting the open graphics API (Vulkan® or OpenGL®) to use for a new application, or when considering the need to migrate an existing application from OpenGL® to Vulkan®, ask yourself the following questions:

  • Do you want to have really low-level access and explicit control over the underlying GPU?
  • Is your application/driver CPU-bound, too slow and consuming too much power?
  • Can your graphic work creation be parallelized and reused?
  • Can you deal with additional code complexity to squeeze out maximum performance?

If your answer to any of the questions is Yes, consider using Vulkan® instead of OpenGL®. However, remember that Vulkan® comes at the cost of taking more responsibility at the application side from the driver.

The following table describes the practical advantages of Vulkan®.

Table: Vulkan® advantages

  OpenGL® Vulkan®
Better GPU control Implicit driver manages the state and resources based on heuristics, leading to overhead and inefficiencies. The application has no control. Explicit API allows you to manage the state and resources as per specific application needs, and relieves you from hidden optimizations giving more control on the GPU.
Multi-core friendly Originally designed for single-threaded architectures and does not allow the generation of graphic commands in parallel to command execution. API is designed around asynchronous generation of command buffers across multiple threads and feeds them in sequence to a command pipeline, which reflects the realities of the modern hardware.
High efficiency Does a lot of redundant excessive validation for each draw call, such as runtime error checking, implicit tracking of resource usage, and synchronization, leading to much CPU overhead. Vulkan® greatly reduces the CPU time spent in the driver with external validation and diagnostics layers that can be independently enabled and disabled, as needed. Offloads the render thread by delegating heavy CPU jobs to the application and opt-in layers.
Shader portability Only GLSL is supported as a shader language, and the compiler is a part of the driver with vendor-specific semantics. No user control over the front end and higher runtime translation time. Vulkan® mandates the use of the intermediate byte code (SPIR-V) by the driver for shaders. This allows offline shader precompilation, and allows you to write shaders in languages other than GLSL.
Code complexity OpenGL® driver manages many tasks inside the driver relieving you from the burden of managing these at the application end. Vulkan® is a much more verbose API, offering more control at the cost of more code complexity and responsibility at the application side.

SDL

SDL (Simple DirectMedia Layer) is a cross-platform software development library. In Tizen, it enables access to graphics hardware using Vulkan®. SDL is used for creating high-performance computer games, multimedia applications, and emulators. It provides a low-level hardware abstraction layer to computer multimedia hardware components. It can run on many operating systems, such as Android™, iOS, Linux, macOS, Windows®, and Tizen. For more information, see the SDL Web site.

You can manage video, audio, some input devices, threads, and timers with SDL. Tizen supports SDL to provide new means of accessing its 3D APIs (Vulkan), which can currently be accessed only through an EFL wrapper library (Evas GL). For more information on Evas GL, see the OpenGL® ES guide.

The following SDL features are currently supported in Tizen:

  • SDL basics function

    Use the SDL.h, SDL_hints.h, SDL_error.h, SDL_log.h, SDL_assert.h, SDL_version.h, SDL_error.h, and SDL_stdinc.h header files.

  • Display and window management

    Use the SDL_video.h and SDL_syswm.h header files.

  • Event handling

    Use the SDL_events.h and SDL_keyboard.h header files.

  • Audio device management

    Use the SDL_audio.h header file.

  • Thread and timer management

    Use the SDL_thread.h, SDL_mutex.h, SDL_atomic.h, and SDL_timer.h header files.

  • Platform and power information

    Use the SDL_platform.h and SDL_power.h header files.

For the list of features not currently supported in Tizen, see the SDL API (in mobile and wearable applications).

SDL Application Life-cycle in Tizen

The Tizen native application model is responsible for the application life-cycle and system events. The SDL application life-cycle is handled by the SDL_PollEvent() function, which manages the main event loop, the application state change events, and basic system events (general and Tizen-specific).

Figure: SDL application life-cycle

SDL application life-cycle

The SDL application can be in one of several different states, and the state changes are managed by the underlying framework.

Table: SDL application states

State Description
READY Application starts the main loop.
CREATED Application is launched.
RUNNING Application is running and visible to the user.
PAUSED Application is running but invisible to the user.
TERMINATED Application is terminated.

Typically, the application is launched by the user from the Launcher, or by another application. The application is normally launched as the top window, with focus. When the application loses the focus status, the SDL_APP_WILLENTERBACKGROUND and SDL_APP_DIDENTERBACKGROUND events are invoked. The application can go into the pause state, which means that it is not terminated but continues to run in the background. The pause state can happen when:

  • A new application is launched by the request of your application.
  • The user requests to go to the home screen.
  • A system event (such as an incoming phone call) occurs and causes a resident application with a higher priority to become active and temporarily hide your application.
  • An alarm is triggered for another application, which becomes the topmost window and hides your application.

When the application becomes visible again, the SDL_APP_WILLENTERFOREGROUND and SDL_APP_DIDENTERFOREGROUND events are invoked. The visibility returns, when:

  • Another application requests your application to run (such as the Task Navigator, which shows all running applications and lets user select any application to run).
  • All applications on top of your application in the window stack finish.
  • An alarm is triggered for your application, bringing it to the front and hiding other applications.

When the application starts exiting, the SDL_Quit and SDL_TERMINATING events are invoked. Your application can start the termination process, when:

  • The application itself requests to exit by calling the ui_app_exit() or service_app_exit() function to terminate the event loop.
  • The low memory killer is terminating your application in a low memory situation.

Prerequisites

To enable your application to use the Vulkan® functionality:

  1. To use Vulkan® for 3D rendering, you must create an SDL application, and understand both Vulkan® and SDL.
  2. Check whether the device supports Vulkan®.

    As not all Tizen devices available on the market support Vulkan® yet, check for device support with the system_info_get_platform_bool() function before using the Vulkan APIs. If the device can support Vulkan®, the function returns true in the second parameter.

    bool vulkan_support;
    
    system_info_get_platform_bool("http://tizen.org/feature/vulkan.version.1_0", &vulkan_support);
    
  3. To use the functions and data types of the Vulkan (in mobile and wearable applications) and SDL (in mobile and wearable applications) APIs, include the <SDL.h> and <vulkan/vulkan.h> header files in your application:
    #include <SDL.h>
    #include <vulkan/vulkan.h>
    

Rendering a Triangle with Vulkan®

To render a triangle using Vulkan® in an SDL application:

  1. Initialize the SDL library and create an SDL window.

    Before using any other SDL functions, call the SDL_Init() function to properly initialize the SDL library and start each of its various subsystems. The function accepts as a parameter a set of allowed flags OR'd together.

    After SDL is initialized successfully, create the SDL_Window instance using the SDL_CreateWindow() function. The parameters define the title of the window, the X and Y position coordinates, width, height, and a set of SDL_WindowFlags OR'd together.

    Note To use the Vulkan® context, use the SDL_WINDOW_VULKAN flag when you create a window. Do not use both SDL_WINDOW_VULKAN and SDL_WINDOW_OPENGL simultaneously.

    The SDL_main() function is mandatory for the Tizen framework to initialize the SDL application. You must use the SDL_main() function instead of the usual main() function in your SDL application.

    int
    SDL_main(int argc, char *argv[])
    {
        SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS);
        demo.sdl_window = SDL_CreateWindow("SDL Vulkan Sample", 0, 0, demo.sdl_mode.w, demo.sdl_mode.h,
                                           SDL_WINDOW_SHOWN | SDL_WINDOW_FULLSCREEN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_VULKAN);
    }
    
  2. Initialize the Vulkan library.

    Create a Vulkan instance, which is the connection between the application and the Vulkan library. To create an instance:

    1. Fill in a VkApplicationInfo struct with some information about the application:

      • sType specifies the type of the structure. Most Vulkan structs require you to explicitly specify the structure type in the sType member.
      • pNext can point to extension information in the future, but currently leave it NULL for the default initialization.
      • pApplicationName points to a string containing the application name.
      • applicationVersion contains developer-supplied version number of the application.
      • pEngineName is a pointer to a string containing the name of the engine (if any) used to create the application.
      • engineVersion is an unsigned integer variable containing the developer-supplied version number of the engine used to create the application.
      • apiVersion is the version of the Vulkan® API against which the application expects to run.
      const VkApplicationInfo app = {
          .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
          .pNext = NULL,
          .pApplicationName = APP_SHORT_NAME,
          .applicationVersion = 0,
          .pEngineName = APP_SHORT_NAME,
          .engineVersion = 0,
          .apiVersion = VK_API_VERSION_1_0,
      };
      
    2. Vulkan® is a platform-agnostic API, which means that you need an extension to interface with the window system. The SDL_Vulkan_GetInstanceExtensions() SDL function returns the extensions Vulkan® needs to interface with the windowing system. Pass them to the VkInstanceCreateInfo struct.

      Fill in the VkInstanceCreateInfo struct to provide sufficient information for creating an instance. This struct tells the Vulkan® driver which global extensions and validation layers you want to use. Global means that they apply to the entire program and not only a specific device.

      • sType and pNext are similar to the VkApplicationInfo structure.
      • enabledLayerCount is the number of global layers to enable.
      • ppEnabledLayerNames is a pointer to an array containing the names of layers to enable for the created instance.
      • enabledExtensionCount is the number of global extensions to enable.
      • ppEnabledExtensionNames is a pointer to an array of strings containing the names of extensions to enable.
      SDL_Vulkan_GetInstanceExtensions(demo->sdl_window, &(demo->enabled_extension_count), demo->extension_names);
      VkInstanceCreateInfo inst_info = {
          .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
          .pNext = NULL,
          .pApplicationInfo = &app,
          .enabledLayerCount = demo->enabled_layer_count,
          .ppEnabledLayerNames = (const char *const *)instance_validation_layers,
          .enabledExtensionCount = demo->enabled_extension_count,
          .ppEnabledExtensionNames = (const char *const *) demo->extension_names,
      };
      
    3. Call the vkCreateInstance() function to actually create the instance:
      vkCreateInstance(&inst_info, NULL, &demo->inst);
      

      The general pattern that object creation function parameters in Vulkan® follow is:

      • Pointer to the struct with the creation info
      • Pointer to the custom allocator callbacks
      • Pointer to the variable that stores the handle to the new object

      On success, the handle to the instance is returned in the wrapped VkInstance member.

  3. Select a physical device.

    After creating a Vulkan instance, look for and select a graphics card in the system that supports the features you need. You can select any number of graphics cards and use them simultaneously, but the following example only selects the first graphics card. The selected graphics card is stored in a VkPhysicalDevice handle.

    Retrieve the list the graphics cards, store them in an array of the VkPhysicalDevice handles, and select the first graphics card as the physical device:

    VkPhysicalDevice *physical_devices = malloc(sizeof(VkPhysicalDevice) * gpu_count);
    err = vkEnumeratePhysicalDevices(demo->inst, &gpu_count, physical_devices);
    demo->gpu = physical_devices[0];
    
  4. Check for supported queue families.

    Almost every operation in Vulkan®, from drawing to uploading textures, requires commands to be submitted to a "queue". There are different types of queues that originate from different queue families, and each queue family allows only a subset of commands. You need to check which queue families are supported by the device and which one of them supports the commands that you want to use. The following example looks for a queue that supports graphics commands:

    1. Check which queue families are supported by the device with the vkGetPhysicalDeviceQueueFamilyProperties() function:

      VkQueueFamilyProperties *queue_props;
      vkGetPhysicalDeviceQueueFamilyProperties(demo->gpu, &demo->queue_count, demo->queue_props);
      assert(demo->queue_count >= 1);
      
      VkPhysicalDeviceFeatures features;
      vkGetPhysicalDeviceFeatures(demo->gpu, &features);
      
    2. The VkQueueFamilyProperties struct contains details about the queue family, including the type of operations that are supported and the number of queues that can be created based on that family. Look for at least 1 queue family that supports VK_QUEUE_GRAPHICS_BIT:
      uint32_t graphicsQueueNodeIndex = UINT32_MAX;
      for (i = 0; i < demo->queue_count; i++) {
          if ((demo->queue_props[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0) {
              if (graphicsQueueNodeIndex == UINT32_MAX)
                  graphicsQueueNodeIndex = i;
          }
      }
      demo->graphics_queue_node_index = graphicsQueueNodeIndex;
      
  5. Create a logical device.

    After selecting a physical device to use, you need to set up a logical device to interface with it. You can even create multiple logical devices from the same physical device, if you have varying requirements.

    1. Add a new VkDevice type member in which to store the logical device handle:

      VkDevice device;
      

      Logical devices are cleaned up with the vkDestroyDevice() function before the instance is cleaned up.

    2. The creation of a logical device involves specifying a lot of details in structs again. First, fill in the VkDeviceQueueCreateInfo struct, which describes the number of queues you want for a single queue family. In the following example, only 1 queue with graphics capabilities is needed.

      The currently available drivers only allow you to create a low number of queues for each family queue, and you do not really need more than one. That is because you can create all of the command buffers on multiple threads and then submit them all at once on the main thread with a single low-overhead call.

      Vulkan® lets you assign priorities to queues to influence the scheduling of the command buffer execution using floating point numbers between 0.0 and 1.0. This is required even if there is only a single queue.

      float queue_priorities[1] = {0.0};
      const VkDeviceQueueCreateInfo queue = {
          .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
          .pNext = NULL,
          .queueFamilyIndex = demo->graphics_queue_node_index,
          .queueCount = 1,
          .pQueuePriorities = queue_priorities
      };
      
    3. Fill in the main VkDeviceCreateInfo structure.

      First add pointers to the queue creation info and device features structs. The remainder of the information requires you to specify device-specific extensions and validation layers. An example of a device-specific extension is VK_KHR_swapchain, which allows you to present rendered images from that device to windows. The following example enables the same validation layers for devices as before for the instance. It requires no device-specific extensions.

      VkDeviceCreateInfo device = {
          .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
          .pNext = NULL,
          .queueCreateInfoCount = 1,
          .pQueueCreateInfos = &queue,
          .enabledLayerCount = demo->enabled_layer_count,
          .ppEnabledLayerNames = (const char *const *)((demo->validate) ? demo->device_validation_layers : NULL),
          .enabledExtensionCount = demo->enabled_extension_count,
          .ppEnabledExtensionNames = (const char *const *)demo->extension_names,
          .pEnabledFeatures = NULL
      };
      
    4. Instantiate the logical device with the vkCreateDevice() function.

      The parameters are the physical device to interface with, the queue and usage info you just specified, the optional allocation callback pointer, and a pointer to a variable to store the logical device handle in.

      vkCreateDevice(demo->gpu, &device, NULL, &demo->device);
      
  6. Create a window surface.

    Since Vulkan® is a platform-agnostic API, it cannot interface directly with the window system on its own. To establish the connection between Vulkan® and the window system to present results to the screen, you need to use the WSI (Window System Integration) extensions. The VK_KHR_surface extension exposes a VkSurfaceKHR object that represents an abstract type of surface to present rendered images to. The surface in this example program is backed by the window that you have already created using SDL.

    The VK_KHR_surface extension is an instance-level extension and you have actually already enabled it, because it is included in the list returned by the SDL_Vulkan_GetInstanceExtensions() function. Even though the window surface is created before the logical device in this example, it is only mentioned here after the logical device creation, because window surfaces are part of the render targets and presentation part, and explaining them earlier would have cluttered up the basic setup.

    Note Window surfaces are an entirely optional component in Vulkan®, if you only need off-screen rendering. Vulkan® allows you to do that without hacks, such as creating an invisible window (necessary for OpenGL®).
    1. Add a VkSurfaceKHR type variable:

      VkSurfaceKHR surface;
      

      When no longer needed, surfaces are destroyed with the vkDestroySurfaceKHR() function.

    2. Although the VkSurfaceKHR object and its usage is platform-agnostic, its creation is not, because it depends on the window system details. Therefore, there is a platform-specific addition to the extension, which on Tizen is SDL_vulkanSurface and is also automatically included in the list from the SDL_Vulkan_GetInstanceExtensions() function.

      This platform-specific extension on Tizen provides the SDL_Vulkan_CreateSurface() function to create a surface hiding the platform differences for you. The parameters are the SDL window pointer, custom allocators, and pointer to the VkSurfaceKHR variable. It simply passes through the VkResult from the relevant platform call.

      SDL_Vulkan_CreateSurface(demo->sdl_window, (SDL_vulkanInstance)demo->inst, (SDL_vulkanSurface*)&demo->surface);
      
  7. Create the presentation queue.

    Although the Vulkan® implementation can support window system integration, that does not mean that every device in the system supports it. Therefore, you need to ensure that a device can present images to the surface you created. Since the presentation is a queue-specific feature, the problem is actually about finding a queue family that supports presenting to the surface you created.

    1. Look for a queue family that has the capability of presenting to your window surface, by using the vkGetPhysicalDeviceSurfaceSupportKHR() function, which takes the physical device, queue family index, and surface as parameters. Then simply check the value of the boolean and store the presentation family queue index. Note that it is very likely that it ends up being the same queue family as previously selected for the physical device, so the example adds logic to explicitly prefer a physical device that supports drawing and presentation in the same queue for improved performance:

      demo->fpGetPhysicalDeviceSurfaceSupportKHR(demo->gpu, i, demo->surface, &supportsPresent[i]);
      demo->graphics_queue_node_index = graphicsQueueNodeIndex;
      
    2. Add a member variable for the VkQueue handle:
      VkQueue queue;
      
    3. Ideally, you need multiple VkDeviceQueueCreateInfo structs to create a queue from both graphics and presentation queue families. An elegant way to do that is to create a set of all unique queue families that are necessary for the required queues and modify VkDeviceCreateInfo to point to the vector. However, in this example, as the queue families are the same and the 2 handles most likely have the same value, you only need to pass the same index once and retrieve the queue handle:
      vkGetDeviceQueue(demo->device, demo->graphics_queue_node_index, 0, &demo->queue);
      

      Now the queue handle is ready.

  8. Prepare the rendering infrastructure.

    You have successfully created the main handles to access the GPU to request rendering. Now prepare the rendering infrastructure, such as buffers, textures, vertices, render pass, descriptors, commands, and frame buffers, before initiating the actual drawing:

    static void
    demo_prepare(struct demo *demo)
    {
        demo_prepare_buffers(demo);
        demo_prepare_depth(demo);
        demo_prepare_textures(demo);
        demo_prepare_vertices(demo);
        demo_prepare_descriptor_layout(demo);
        demo_prepare_render_pass(demo);
        demo_prepare_pipeline(demo);
    
        demo_prepare_descriptor_pool(demo);
        demo_prepare_descriptor_set(demo);
    
        demo_prepare_framebuffers(demo);
    
        demo->prepared = true;
    }
    
  9. Initiate rendering from the main loop.

    Now you are set to perform continuous rendering from the application main loop. Initiate the main loop and call the rendering routine as well as start polling for user input events:

    while(1) {
        while (SDL_PollEvent(&event)) {
            printf("SDL Event type :: %d\n", event.type);
            if (event.type == SDL_MOUSEBUTTONDOWN)
                printf("SDL_MOUSEBUTTONDOWN Event!!\n");
            if (event.type == SDL_MOUSEMOTION)
                printf("SDL_MOUSEMOTION Event!!\n");
        }
        demo_run(&demo);
    }
    
  10. Before exiting the application, destroy all the Vulkan resources as well as the SDL window that needs to be explicitly freed up:
    demo_cleanup(&demo);
    SDL_DestroyWindow(demo.sdl_window);
    
    SDL_Quit();
    

Handling General SDL Events

To handle general SDL events:

  1. Call the SDL_PollEvent() function, which polls the currently pending events and returns the SDL_Event instance. Before calling the SDL_PollEvent() function, create an empty SDL_Event structure.

    void
    updateApp(appdata_s* ad)
    {
        /* SDL_Event is a union containing structures for different event types */
        SDL_Event event;
        while (SDL_PollEvent(&event)) {
            SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "[SDL] Event type: %x\n", event.type);
            handleEvent(&ad, &event);
        }
    }
    
  2. The SDL_PollEvent() function removes the next event from the event queue. If there is no event in the queue, it returns 0. If there is an event, it fills the SDL_Event object with the event information.

    The SDL_Event object is a union that contains structures for the different event types. The type member specifies the event type, shared with all events. The type member is related to the SDL_EventType enumeration.

    To handle each event type separately, use a switch statement:

    void
    handleEvent(appdata_s** data, SDL_Event* event)
    {
        appdata_s* ad = *data;
    
        switch (event->type) {
        case SDL_QUIT:
            SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "[SDL] Finish main loop ");
            ad->game_exit = 1;
            break;
        case SDL_KEYUP:
            char* scancodename = (char *)SDL_GetScancodeName(event->key.keysym.scancode);
            SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "[SDL] keyboard scancode: %s", scancodename);
            if (event->key.keysym.scancode == SDL_SCANCODE_AC_BACK) {
                SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "[SDL] Finish main loop ");
                ad->game_exit = 1;
            }
            break;
        case SDL_MOUSEBUTTONDOWN:
            SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "[SDL]Mouse Down: %d x %d", event->button.x, event->button.y);
            break;
        case SDL_MOUSEBUTTONUP:
            SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "[SDL]Mouse Up: %d x %d", event->button.x, event->button.y);
            break;
        case SDL_MOUSEMOTION:
            SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "[SDL]Mouse Motion: %d x %d", event->motion.x, event->motion.y);
            break;
        case SDL_ROTATEEVENT:
            ad->window_rotation = (int)event->user.data1;
            SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "[SDL] Rotation degree: %d", ad->window_rotation);
            break;
        case SDL_WINDOWEVENT:
            SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "SDL_WINDOWEVENT Event!!");
            if (event->window.event == SDL_WINDOWEVENT_SIZE_CHANGED)
                SDL_Log("SDL_WINDOWEVENT_SIZE_CHANGED!!!");
            break;
        } /* End switch */
    }
    

Handling Tizen-specific SDL Events

To handle SDl events specifically added to the Tizen application framework:

  • SDL_APP_CONTROL

    This event is invoked when the application is launched with some parameters. In Tizen, this event is called in the _tizen_sdl_control() function.

    The application framework calls the application's application control callback just after the application enters the main loop. This callback is passed to the app_control instance containing the reason why the application was launched. For example, the application can be launched to open a file to handle the request that has been sent by another application. In any case, the application is responsible for checking the app_control content and responding appropriately. The app_control content can be empty, if the application is launched from the launcher.

    In SDL, SDL_APP_CONTROL has been defined as a new SDL_Event event type for the application control. After the application enters the main loop, SDL sends the SDL_APP_CONTROL event to the application. This means that the application can confirm the SDL_Event in the event loop. The event is defined as an SDL_UserEvent, which is in the user member of the SDL_Event union.

    The user structure contains data1 (app_control) and data2 (user_data).

    In Tizen, you must include the <app.h> header file to use the application control. For more information, see the Application Controls guide.

    #include <app.h>
    
    case SDL_APP_CONTROL:
        app_control_h app_control = event.user.data1;
        void *user_data = event.user.data2;
    
        char *operation;
        char *uri;
    
        app_control_get_operation(app_control, &operation);
        if (!strcmp(operation, APP_CONTROL_OPERATION_VIEW)) {
            app_control_get_uri(app_control, &uri);
            app_control_get_extra_data(app_control, "action", &action);
            SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Get path: [%s], action: [%s]", uri, action);
        }
    break;
    
  • SDL_APP_LOWBATTERY

    This event is invoked when the application is low on battery. Use it to reduce power consumption, if possible. In Tizen, this event is called in the _tizen_app_low_battery() function.

    Get the low battery status from the given event info by calling the app_event_get_low_battery_status() function. The app_event_low_battery_status_e is the enumeration for the battery status: APP_EVENT_LOW_BATTERY_POWER_OFF means that the battery charge is under 1% and APP_EVENT_LOW_BATTERY_CRITICAL_LOW under 5%.

    #include <app.h>
    
    case SDL_APP_LOWBATTERY:
        SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "[SDL] SDL_APP_LOWBATTERY ");
    
        app_event_info_h event_info = event->user.data1;
        void *user_data = event->user.data2;
    
        app_event_low_battery_status_e status;
        int ret = app_event_get_low_battery_status(event_info, &status);
        if (ret == APP_ERROR_NONE) {
            if (status == APP_EVENT_LOW_BATTERY_POWER_OFF)
                SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "[SDL] The battery status is under 1% ");
            else if (status == APP_EVENT_LOW_BATTERY_CRITICAL_LOW)
                SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "[SDL] The battery status is under 5% ");
        }
        break;
    
  • SDL_APP_LANGUAGE_CHANGED

    This event is invoked when the displayed language is changed by the system. In Tizen, this event is called in the _tizen_app_lang_changed() function.

    Get the language from the given event info by calling the app_event_get_language() function.

    #include <app.h>
    
    case SDL_APP_LANGUAGE_CHANGED:
        SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "[SDL] SDL_APP_LANGUAGE_CHANGED ");
    
        app_event_info_h event_info = event->user.data1;
        void *user_data = event->user.data2;
    
        char *language;
        int ret = app_event_get_language(event_info, &language);
        if (ret != APP_ERROR_NONE)
            SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "[SDL] app_event_get_language failed. Err = %d ", ret);
    
        if (language != NULL) {
            SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "[SDL] language:  ", language);
            free(language);
        }
        break;
    
  • SDL_ROTATEEVENT

    This event is invoked when the device orientation changes. In Tizen, this event is called in the _tizen_app_orient_changed() function.

    Tizen supports portrait and landscape screen orientations, and you must take care of how your application responds to rotation changes. Use the SDL_SetHint() function to set the orientations with SDL_HINT_ORIENTATIONS. A hint specifies a variable controlling which orientations are allowed in Tizen.

    Table: Allowed orientations

    Orientation Description
    LandscapeLeft Top of the device on the left
    LandscapeRight Top of the device on the right
    Portrait Top of device up
    SDL_SetHint(SDL_HINT_ORIENTATIONS, "Portrait LandscapeLeft LandscapeRight");
    
    case SDL_ROTATEEVENT:
        ad->window_rotation = (int)event->user.data1;
        SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "[SDL] Rotation degree: %d", ad->window_rotation);
        break;