Escuela Técnica Superior de Ingeniería

 

Grado en Ingeniería Informática

Animación por Ordenador

Curso 2025/2026

 

Práctica 1e

Selección del dispositivo físico

 

Objetivos

 

Añade al proyecto la selección del dispositivo físico (tarjeta gráfica) a utilizar para generar las imágenes. Junto a la referencia al dispositivo también se almacenan las propiedades de los diferentes tipos de memoria incluidas en el dispositivo y las familias de colas que soportan comandos de renderizado y de presentación.

 

 

Dispositivos físicos

 

Un dispositivo físico se representa mediante una valor de tipo VkPhysicalDevice, que no es más que un descriptor (handle). Para obtener los dispositivos disponibles se ejecuta dos veces la función vkEnumeratePhysicalDevices(), una vez para obtener el número de dispositivos y la segunda para obtener los descriptores:

VkResult vkEnumeratePhysicalDevices ( 
  VkInstance        instance, 
  uint32_t*         pPhysicalDeviceCount, 
  VkPhysicalDevice* pPhysicalDevices);

A partir de la referencia a un dispositivo físico se pueden obtener sus propiedades, sus características y los tipos de memoria que utiliza internamente. 

void vkGetPhysicalDeviceProperties ( 
  VkPhysicalDevice            physicalDevice, 
  VkPhysicalDeviceProperties* pProperties); 
  
void vkGetPhysicalDeviceFeatures ( 
  VkPhysicalDevice          physicalDevice, 
  VkPhysicalDeviceFeatures* pFeatures); 

void vkGetPhysicalDeviceMemoryProperties ( 
  VkPhysicalDevice                  physicalDevice, 
  VkPhysicalDeviceMemoryProperties* pMemoryProperties);

Las propiedades de un dispositivo físico se expresan en una estructura denominada VkPhysicalDeviceProperties que contiene campos como el nombre del dispositivo, el fabricante o la versión del driver utilizado.

typedef struct VkPhysicalDeviceProperties {
  uint32_t                         apiVersion;
  uint32_t                         driverVersion;
  uint32_t                         vendorID;
  uint32_t                         deviceID;
  VkPhysicalDeviceType             deviceType;
  char                             deviceName[VK_MAX_PHYSICAL_DEVICE_NAME_SIZE];
  uint8_t                          pipelineCacheUUID[VK_UUID_SIZE];
  VkPhysicalDeviceLimits           limits;
  VkPhysicalDeviceSparseProperties sparseProperties;
} VkPhysicalDeviceProperties;

El tipo de dispositivo se describe en el campo deviceType que corresponde a un valor del tipo enumerado VkPhysicalDeviceType. Este valor indica si el dispositivo se trata de una tarjeta gráfica integrada o es una tarjeta gráfica dedicada o virtualizada, etc.

typedef enum VkPhysicalDeviceType {
    VK_PHYSICAL_DEVICE_TYPE_OTHER = 0,
    VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU = 1,
    VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU = 2,
    VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU = 3,
    VK_PHYSICAL_DEVICE_TYPE_CPU = 4,
} VkPhysicalDeviceType;

Muchas de las propiedades del dispositivo se encuentran descritas en el campo limits que almacena una estructura de tipo VkPhysicalDeviceLimits formada por numerosos campos que describen los valores máximos y mínimos soportados por el dispositivo para distintas propiedades.

typedef struct VkPhysicalDeviceLimits {
    uint32_t              maxImageDimension1D;
    uint32_t              maxImageDimension2D;
    uint32_t              maxImageDimension3D;
    uint32_t              maxImageDimensionCube;
    uint32_t              maxImageArrayLayers;
    uint32_t              maxTexelBufferElements;
    uint32_t              maxUniformBufferRange;
    uint32_t              maxStorageBufferRange;
    uint32_t              maxPushConstantsSize;
    uint32_t              maxMemoryAllocationCount;
    uint32_t              maxSamplerAllocationCount;
    VkDeviceSize          bufferImageGranularity;
    VkDeviceSize          sparseAddressSpaceSize;
    uint32_t              maxBoundDescriptorSets;
    uint32_t              maxPerStageDescriptorSamplers;
    uint32_t              maxPerStageDescriptorUniformBuffers;
    uint32_t              maxPerStageDescriptorStorageBuffers;
    uint32_t              maxPerStageDescriptorSampledImages;
    uint32_t              maxPerStageDescriptorStorageImages;
    uint32_t              maxPerStageDescriptorInputAttachments;
    uint32_t              maxPerStageResources;
    uint32_t              maxDescriptorSetSamplers;
    uint32_t              maxDescriptorSetUniformBuffers;
    uint32_t              maxDescriptorSetUniformBuffersDynamic;
    uint32_t              maxDescriptorSetStorageBuffers;
    uint32_t              maxDescriptorSetStorageBuffersDynamic;
    uint32_t              maxDescriptorSetSampledImages;
    uint32_t              maxDescriptorSetStorageImages;
    uint32_t              maxDescriptorSetInputAttachments;
    uint32_t              maxVertexInputAttributes;
    uint32_t              maxVertexInputBindings;
    uint32_t              maxVertexInputAttributeOffset;
    uint32_t              maxVertexInputBindingStride;
    uint32_t              maxVertexOutputComponents;
    uint32_t              maxTessellationGenerationLevel;
    uint32_t              maxTessellationPatchSize;
    uint32_t              maxTessellationControlPerVertexInputComponents;
    uint32_t              maxTessellationControlPerVertexOutputComponents;
    uint32_t              maxTessellationControlPerPatchOutputComponents;
    uint32_t              maxTessellationControlTotalOutputComponents;
    uint32_t              maxTessellationEvaluationInputComponents;
    uint32_t              maxTessellationEvaluationOutputComponents;
    uint32_t              maxGeometryShaderInvocations;
    uint32_t              maxGeometryInputComponents;
    uint32_t              maxGeometryOutputComponents;
    uint32_t              maxGeometryOutputVertices;
    uint32_t              maxGeometryTotalOutputComponents;
    uint32_t              maxFragmentInputComponents;
    uint32_t              maxFragmentOutputAttachments;
    uint32_t              maxFragmentDualSrcAttachments;
    uint32_t              maxFragmentCombinedOutputResources;
    uint32_t              maxComputeSharedMemorySize;
    uint32_t              maxComputeWorkGroupCount[3];
    uint32_t              maxComputeWorkGroupInvocations;
    uint32_t              maxComputeWorkGroupSize[3];
    uint32_t              subPixelPrecisionBits;
    uint32_t              subTexelPrecisionBits;
    uint32_t              mipmapPrecisionBits;
    uint32_t              maxDrawIndexedIndexValue;
    uint32_t              maxDrawIndirectCount;
    float                 maxSamplerLodBias;
    float                 maxSamplerAnisotropy;
    uint32_t              maxViewports;
    uint32_t              maxViewportDimensions[2];
    float                 viewportBoundsRange[2];
    uint32_t              viewportSubPixelBits;
    size_t                minMemoryMapAlignment;
    VkDeviceSize          minTexelBufferOffsetAlignment;
    VkDeviceSize          minUniformBufferOffsetAlignment;
    VkDeviceSize          minStorageBufferOffsetAlignment;
    int32_t               minTexelOffset;
    uint32_t              maxTexelOffset;
    int32_t               minTexelGatherOffset;
    uint32_t              maxTexelGatherOffset;
    float                 minInterpolationOffset;
    float                 maxInterpolationOffset;
    uint32_t              subPixelInterpolationOffsetBits;
    uint32_t              maxFramebufferWidth;
    uint32_t              maxFramebufferHeight;
    uint32_t              maxFramebufferLayers;
    VkSampleCountFlags    framebufferColorSampleCounts;
    VkSampleCountFlags    framebufferDepthSampleCounts;
    VkSampleCountFlags    framebufferStencilSampleCounts;
    VkSampleCountFlags    framebufferNoAttachmentsSampleCounts;
    uint32_t              maxColorAttachments;
    VkSampleCountFlags    sampledImageColorSampleCounts;
    VkSampleCountFlags    sampledImageIntegerSampleCounts;
    VkSampleCountFlags    sampledImageDepthSampleCounts;
    VkSampleCountFlags    sampledImageStencilSampleCounts;
    VkSampleCountFlags    storageImageSampleCounts;
    uint32_t              maxSampleMaskWords;
    VkBool32              timestampComputeAndGraphics;
    float                 timestampPeriod;
    uint32_t              maxClipDistances;
    uint32_t              maxCullDistances;
    uint32_t              maxCombinedClipAndCullDistances;
    uint32_t              discreteQueuePriorities;
    float                 pointSizeRange[2];
    float                 lineWidthRange[2];
    float                 pointSizeGranularity;
    float                 lineWidthGranularity;
    VkBool32              strictLines;
    VkBool32              standardSampleLocations;
    VkDeviceSize          optimalBufferCopyOffsetAlignment;
    VkDeviceSize          optimalBufferCopyRowPitchAlignment;
    VkDeviceSize          nonCoherentAtomSize;
} VkPhysicalDeviceLimits;

Las características del dispositivo se describen en una estructura de tipo VkPhysicalDeviceFeatures, que está formada por numerosos flags.

typedef struct VkPhysicalDeviceFeatures {
  VkBool32    robustBufferAccess;
  VkBool32    fullDrawIndexUint32;
  VkBool32    imageCubeArray;
  VkBool32    independentBlend;
  VkBool32    geometryShader;
  VkBool32    tessellationShader;
  VkBool32    sampleRateShading;
  VkBool32    dualSrcBlend;
  VkBool32    logicOp;
  VkBool32    multiDrawIndirect;
  VkBool32    drawIndirectFirstInstance;
  VkBool32    depthClamp;
  VkBool32    depthBiasClamp;
  VkBool32    fillModeNonSolid;
  VkBool32    depthBounds;
  VkBool32    wideLines;
  VkBool32    largePoints;
  VkBool32    alphaToOne;
  VkBool32    multiViewport;
  VkBool32    samplerAnisotropy;
  VkBool32    textureCompressionETC2;
  VkBool32    textureCompressionASTC_LDR;
  VkBool32    textureCompressionBC;
  VkBool32    occlusionQueryPrecise;
  VkBool32    pipelineStatisticsQuery;
  VkBool32    vertexPipelineStoresAndAtomics;
  VkBool32    fragmentStoresAndAtomics;
  VkBool32    shaderTessellationAndGeometryPointSize;
  VkBool32    shaderImageGatherExtended;
  VkBool32    shaderStorageImageExtendedFormats;
  VkBool32    shaderStorageImageMultisample;
  VkBool32    shaderStorageImageReadWithoutFormat;
  VkBool32    shaderStorageImageWriteWithoutFormat;
  VkBool32    shaderUniformBufferArrayDynamicIndexing;
  VkBool32    shaderSampledImageArrayDynamicIndexing;
  VkBool32    shaderStorageBufferArrayDynamicIndexing;
  VkBool32    shaderStorageImageArrayDynamicIndexing;
  VkBool32    shaderClipDistance;
  VkBool32    shaderCullDistance;
  VkBool32    shaderFloat64;
  VkBool32    shaderInt64;
  VkBool32    shaderInt16;
  VkBool32    shaderResourceResidency;
  VkBool32    shaderResourceMinLod;
  VkBool32    sparseBinding;
  VkBool32    sparseResidencyBuffer;
  VkBool32    sparseResidencyImage2D;
  VkBool32    sparseResidencyImage3D;
  VkBool32    sparseResidency2Samples;
  VkBool32    sparseResidency4Samples;
  VkBool32    sparseResidency8Samples;
  VkBool32    sparseResidency16Samples;
  VkBool32    sparseResidencyAliased;
  VkBool32    variableMultisampleRate;
  VkBool32    inheritedQueries;
} VkPhysicalDeviceFeatures;

Las tarjetas gráficas contienen diferentes tipos de memoria interna. La descripción de los tipos de memoria incluidos en un dispositivo se representa en una estructura VkPhysicalDeviceMemoryProperties .

typedef struct VkPhysicalDeviceMemoryProperties {
  uint32_t     memoryTypeCount;
  VkMemoryType memoryTypes[VK_MAX_MEMORY_TYPES];
  uint32_t     memoryHeapCount;
  VkMemoryHeap memoryHeaps[VK_MAX_MEMORY_HEAPS];
} VkPhysicalDeviceMemoryProperties;

Los tipos de memoria se describen en estructuras VkMemoryType. Estas indican el heap al que está vinculada cada memoria y un campo propertyFlags que indica si la memoria es accesible con rapidez desde el interior del dispositivo y desde el exterior.

typedef struct VkMemoryType {
    VkMemoryPropertyFlags    propertyFlags;
    uint32_t                 heapIndex;
} VkMemoryType;

typedef enum VkMemoryPropertyFlagBits {
    VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT = 0x00000001,
    VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT = 0x00000002,
    VK_MEMORY_PROPERTY_HOST_COHERENT_BIT = 0x00000004,
    VK_MEMORY_PROPERTY_HOST_CACHED_BIT = 0x00000008,
    VK_MEMORY_PROPERTY_LAZILY_ALLOCATED_BIT = 0x00000010,
  // Provided by VK_VERSION_1_1
    VK_MEMORY_PROPERTY_PROTECTED_BIT = 0x00000020,
  // Provided by VK_AMD_device_coherent_memory
    VK_MEMORY_PROPERTY_DEVICE_COHERENT_BIT_AMD = 0x00000040,
  // Provided by VK_AMD_device_coherent_memory
    VK_MEMORY_PROPERTY_DEVICE_UNCACHED_BIT_AMD = 0x00000080,
  // Provided by VK_NV_external_memory_rdma
    VK_MEMORY_PROPERTY_RDMA_CAPABLE_BIT_NV = 0x00000100,
} VkMemoryPropertyFlagBits;

Para enviar trabajos a los dispositivos se utilizan colas (queue). Los dispositivos pueden trabajar con varias colas en paralelo. Las colas se distribuyen en familias, que son colas con las mismas características. Dado un dispositivo, se pueden obtener las familias de colas soportadas utilizando vkGetPhysicalDeviceQueueFamilyProperties(). Esta función se suele ejecutar dos veces para obtener primero el número de familias de colas soportadas y luego las propiedades de estas familias.

void vkGetPhysicalDeviceQueueFamilyProperties (
  VkPhysicalDevice         physicalDevice,
  uint32_t*                pQueueFamilyPropertyCount, 
  VkQueueFamilyProperties* pQueueFamilyProperties);

Las propiedades de las familias de colas se expresan mediante la estructura VkQueueFamilyProperties. El campo queueFlags indica el uso que permite una determinada familia: operaciones gráficas, operaciones de computación, operaciones de transferencia, etc. El campo queueCount indica el número de colas de esa familia que pueden ejecutarse en paralelo en el dispositivo.

typedef struct VkQueueFamilyProperties { 
  VkQueueFlags queueFlags; 
  uint32_t     queueCount; 
  uint32_t     timestampValidBits; 
  VkExtent3D   minImageTransferGranularity; 
} VkQueueFamilyProperties;

El tipo de dato VkQueueFlags permite encapsular diferentes opciones en forma de bits. Los valores definidos para estos bits están descritos en la enumeración VkQueueFlagBits.

typedef enum VkQueueFlagBits {
    VK_QUEUE_GRAPHICS_BIT = 0x00000001,
    VK_QUEUE_COMPUTE_BIT = 0x00000002,
    VK_QUEUE_TRANSFER_BIT = 0x00000004,
    VK_QUEUE_SPARSE_BINDING_BIT = 0x00000008,
    VK_QUEUE_PROTECTED_BIT = 0x00000010,
    VK_QUEUE_VIDEO_DECODE_BIT_KHR = 0x00000020,
    VK_QUEUE_VIDEO_ENCODE_BIT_KHR = 0x00000040,
} VkQueueFlagBits;

No todos los dispositivos físicos soportan la capacidad de presentar imágenes sobre una superficie. Para comprobarlo se puede utilizar una función genérica o una función específica de la plataforma utilizada. La comprobación se realiza sobre las familias de colas que soporta el dispositivo.

VkResult vkGetPhysicalDeviceSurfaceSupportKHR( 
  VkPhysicalDevice physicalDevice, 
  uint32_t         queueFamilyIndex,
  VkSurfaceKHR     surface, 
  VkBool32*        pSupported);

VkBool32 vkGetPhysicalDeviceWin32PresentationSupportKHR(
  VkPhysicalDevice physicalDevice, 
  uint32_t         queueFamilyIndex);

 

 

Modificaciones de la clase GEGraphicsContext

 

Para incluir el dispositivo físico (una estructura VkPhysicalDevice) es necesario modificar la clase GEGraphicsContext. La modificación consiste en añadir el campo physicalDevice, el campo memProperties y los campos graphicsQueueFamilyIndex y presentQueueFamilyIndex.

#pragma once

#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
#include <vulkan/vulkan.h>

class GEGraphicsContext
{
public:
  VkInstance instance;
  VkSurfaceKHR surface;
  VkPhysicalDevice physicalDevice;
  uint32_t graphicsQueueFamilyIndex;
  uint32_t presentQueueFamilyIndex;

private:
  VkPhysicalDeviceMemoryProperties memProperties;

public:
  GEGraphicsContext(GLFWwindow* window);
  ~GEGraphicsContext();
  uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties);
  VkFormat findDepthFormat();

private:
  // Métodos de inicialización de Vulkan
  void createInstance();
  void createSurface(GLFWwindow* window);
  void pickPhysicalDevice();

  // Métodos auxiliares
  void showInstanceProperties();
  bool isDeviceSuitable(VkPhysicalDevice pDevice);
  void showDevices();
  void resumeDeviceProperties(VkPhysicalDevice pDevice, int index);
};

En el código de la clase es necesario añadir algunos métodos y modificar otros.

  • GEGraphicsContext(GLFWwindow* window): Constructor de la clase. Se ha modificado para seledccionar el dispositivo físico y mostrar las propiedades de todos los dispositivos disponibles.

  • findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties): Busca entre las diferentes memorias del dispositivo una que cumpla con las propiedades solicitadas.

  • findDepthFormat(): Busca el formato adecuado para representar los datos de profundidad. El buffer de profundidad se utiliza al generar las imágenes para detectar si un objeto está por delante de otro. Existen diferentes formatos que pueden utilizarse para almacenar la profundiad y no todos los dispositivos soportan todos los formatos.

  • pickPhysicalDevice(): Selecciona el dispositivo físico a utilizar para generar los gráficos. Debe ser un dispositivo que soporte tanto la generación de gráficos como la presentación de los gráficos generados.Obtiene la lista de dispositivos disponibles llamando dos veces a la función vkEnumeratePhysicalDevices() y verifica si el dispositivo es seleccionable llamando al método auxiliar isDeviceSuitable(). Una vez seleccionado el dispositivo almacena las propiedades de las memorias para facilitar las búsquedas de memorias posteriores.

  •  isDeviceSuitable(): Estudia si un cierto dispositivo físico admite la generación de gráficos y su presentación. Para que un dispositivo pueda generar gráficos es necesario que admita alguna cola que soporte la generación de gráficos. Por tanto hay que examinar las familias de colas disponibles en el dispositivo y estudiar si las propiedades de alguna de ellas contiene el bit VK_QUEUE_GRAPHICS_BIT que indica esta propiedad de las colas. Para saber si una familia de colas soporta la presentación sobre la superficie creada se utiliza el método vkGetPhysicalDeviceSurfaceSupportKHR(). SAl mismo tiempo que se estudia el dispositivo se asigna el valor de los campos graphicsQueueFamilyIndex y presentQueueFamilyIndex y se intenta escoger una familia que soorte las dos características.

  • showDevices(): Recorre todos los dispositivos generando un fichero con sus propiedades.

  •  resumeDeviceProperties(VkPhysicalDevice pDevice, int index): Lee las propiedades de un dispositivo y genera un fichero describiendo estas propiedades.

///////////////////////////////////////////////////////////////////////////////////
/////                                                                         /////
/////                          Métodos públicos                               /////
/////                                                                         /////
///////////////////////////////////////////////////////////////////////////////////

//
// FUNCIÓN: GEGraphicsContext::GEGraphicsContext()
//
// PROPÓSITO: Crea un contexto gráfico de Vulkan (instancia, superficie, dispositivo)
//
GEGraphicsContext::GEGraphicsContext(GLFWwindow* window)
{
  createInstance();
  createSurface(window);
  // showInstanceProperties();
  pickPhysicalDevice();
  showDevices();
}

//
// FUNCIÓN: GEGraphicsContext::findMemoryType()
//
// PROPÓSITO: Busca el tipo de memoria adecuado para el filtro indicado
//
uint32_t GEGraphicsContext::findMemoryType(uint32_t typeFilter,
                                           VkMemoryPropertyFlags properties)
{
  for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++)
  {
    if ((typeFilter & (1 << i)) && 
        (memProperties.memoryTypes[i].propertyFlags & properties) == properties)
    {
      return i;
    }
  }
  throw std::runtime_error("failed to find suitable memory type!");
}

//
// FUNCIÓN: GEGraphicsContext::findDepthFormat()
//
// PROPÓSITO: Busca el formato adecuado para el buffer de profundidad
//
VkFormat GEGraphicsContext::findDepthFormat()
{
  VkFormat candidates[] = { VK_FORMAT_D32_SFLOAT, 
                            VK_FORMAT_D32_SFLOAT_S8_UINT, 
                            VK_FORMAT_D24_UNORM_S8_UINT };
  VkImageTiling tiling = VK_IMAGE_TILING_OPTIMAL;
  VkFormatFeatureFlags features = VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT;

  for (VkFormat format : candidates)
  {
    VkFormatProperties props;
    vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &props);

    if (tiling == VK_IMAGE_TILING_LINEAR && 
        (props.linearTilingFeatures & features) == features)
    {
      return format;
    }
    else if (tiling == VK_IMAGE_TILING_OPTIMAL && 
             (props.optimalTilingFeatures & features) == features)
    {
      return format;
    }
  }

  throw std::runtime_error("failed to find supported format!");
}

///////////////////////////////////////////////////////////////////////////////////
/////                                                                         /////
/////                 Métodos de inicialización de Vulkan                     /////
/////                                                                         /////
///////////////////////////////////////////////////////////////////////////////////

...

//
// FUNCIÓN: GEGraphicsContext::pickPhysicalDevice()
//
// PROPÓSITO: Selecciona el dispositivo físico sobre el que generar los gráficos
//
void GEGraphicsContext::pickPhysicalDevice()
{
  uint32_t deviceCount = 0;
  vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);

  if (deviceCount == 0)
  {
    throw std::runtime_error("failed to find GPUs with Vulkan support!");
  }

  std::vector<VkPhysicalDevice> devices(deviceCount);
  vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());

  for (unsigned int i = 0; i < deviceCount; i++)
  {
    VkPhysicalDeviceProperties deviceProperties;
    vkGetPhysicalDeviceProperties(devices[i], &deviceProperties);

    if (deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU && 
        isDeviceSuitable(devices[i]))
    {
      physicalDevice = devices[i];
      break;
    }
  }

  if (physicalDevice == VK_NULL_HANDLE)
  {
    for (unsigned int i = 0; i < deviceCount; i++)
    {
      if (isDeviceSuitable(devices[i]))
      {
        physicalDevice = devices[i];
        break;
      }
    }
  }

  if (physicalDevice == VK_NULL_HANDLE)
  {
    throw std::runtime_error("failed to find a suitable GPU!");
  }

  vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties);
}

///////////////////////////////////////////////////////////////////////////////////
/////                                                                         /////
/////                          Métodos auxiliares                             /////
/////                                                                         /////
///////////////////////////////////////////////////////////////////////////////////

//
// FUNCIÓN: GEGraphicsContext::isDeviceSuitable(VkPhysicalDevice pDevice)
//
// PROPÓSITO: Verifica si un dispositivo físico admite generación de gráficos
//
bool GEGraphicsContext::isDeviceSuitable(VkPhysicalDevice pDevice)
{
  uint32_t queueFamilyCount = 0;
  vkGetPhysicalDeviceQueueFamilyProperties(pDevice, &queueFamilyCount, nullptr);
  std::vector< VkQueueFamilyProperties> queueFamilies(queueFamilyCount);
  vkGetPhysicalDeviceQueueFamilyProperties(pDevice, &queueFamilyCount, 
                                                    queueFamilies.data());

  bool graphics = false;
  bool present = false;
  for (uint32_t i = 0; i < queueFamilyCount; i++)
  {
    VkBool32 presentSupport = false;
    vkGetPhysicalDeviceSurfaceSupportKHR(pDevice, i, surface, &presentSupport);
    if (presentSupport && (queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT))
    {
      graphicsQueueFamilyIndex = i;
      presentQueueFamilyIndex = i;
      graphics = true;
      present = true;
      break;
    }
  }

  if (!(graphics && present))
  {
    for (uint32_t i = 0; i < queueFamilyCount; i++)
    {
      if (queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT)
      {
        graphicsQueueFamilyIndex = i;
        graphics = true;
      }

      VkBool32 presentSupport = false;
      vkGetPhysicalDeviceSurfaceSupportKHR(pDevice, i, surface, &presentSupport);
      if (presentSupport)
      {
        presentQueueFamilyIndex = i;
        present = true;
      }
    }
  }

  if (graphics && present) return true;

  return false;
}
//
// FUNCIÓN: GEGraphicsContext::showInstanceProperties()
//
// PROPÓSITO: Muestra los dispositivos físicos disponibles 
// y genera un archivo con las propiedades de cada uno
//
void GEGraphicsContext::showDevices()
{
  uint32_t deviceCount = 0;
  vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);
  std::vector<VkPhysicalDevice> devices(deviceCount);
  vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());

  // Mostrar los dispositivos físicos disponibles
  std::cout << "Physical devices:" << std::endl;
  for (unsigned int i = 0; i < deviceCount; i++)
  {
    VkPhysicalDeviceProperties deviceProperties;
    vkGetPhysicalDeviceProperties(devices[i], &deviceProperties);
    std::cout << "\t" << deviceProperties.deviceName << std::endl;

    resumeDeviceProperties(devices[i], i);
  }

  std::cout << "Selected device:" << std::endl;
  VkPhysicalDeviceProperties deviceProperties;
  vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties);
  std::cout << "\t" << deviceProperties.deviceName << std::endl;
}

//
// FUNCIÓN: GEGraphicsContext::resume(VkPhysicalDevice pDevice, int index)
//
// PROPÓSITO: Escribe las caracterísiticas del dispositivo físico
//
#define BOOL(x) ( (x)!=0? "true" : "false")
void GEGraphicsContext::resumeDeviceProperties(VkPhysicalDevice pDevice, int index)
{
  ...
}

 

 

Aspecto final

 

El aspecto de la aplicación es el siguiente. La consola muestra la lista de dispositivos disponibles compatibles con Vulkan. Esta información depende de la plataforma en la que se esté ejecutando la aplicación. La ejecución también genera el fichero "deviceX.txt" con las características de cada dispositivo.

Figura20