Skip to content

SimpleEngine - missing textures #383

@ian-m-carr

Description

@ian-m-carr

Can someone confirm the status of the SimpleEngine codebase?

I have built from (Main) and am trying to load the bistro scene with KTX2 textures.

as the source stands I see the scene rendered but largely without textures (mostly grey scene).

The console contains many entries of the following form:

Parsing GLTF file: ../Assets/bistro/bistro.gltf
Using base texture path: /opt/imc-home-extra/projects/Vulkan/SimpleEngine/cmake-build-debug/../Assets/bistro/
[LoadTextureFromMemory] start id=gltf_baseColor_127 -> resolved=gltf_baseColor_127 size=0x0 ch=0
[LoadTextureFromMemory] start id=gltf_specGloss_128 -> resolved=gltf_specGloss_128 size=0x0 ch=0
[LoadTextureFromMemory] start id=gltf_texture_1 -> resolved=gltf_texture_1 size=0x0 ch=0
[LoadTextureFromMemory] start id=gltf_texture_0 -> resolved=gltf_texture_0 size=0x0 ch=0
[LoadTextureFromMemory] start id=gltf_baseColor_129 -> resolved=gltf_baseColor_129 size=0x0 ch=0
[LoadTextureFromMemory] start id=gltf_baseColor_130 -> resolved=gltf_baseColor_130 size=0x0 ch=0
[LoadTextureFromMemory] start id=gltf_specGloss_131 -> resolved=gltf_specGloss_131 size=0x0 ch=0
[LoadTextureFromMemory] start id=gltf_texture_2 -> resolved=gltf_texture_2 size=0x0 ch=0
Validation layer: vkCreateBuffer(): pCreateInfo->size is zero.
The Vulkan spec states: size must be greater than 0 (https://docs.vulkan.org/spec/latest/chapters/resources.html#VUID-VkBufferCreateInfo-size-00912)
LoadTextureFromMemory: Invalid parameters
Validation layer: vkCreateBuffer(): pCreateInfo->size is zero.
The Vulkan spec states: size must be greater than 0 (https://docs.vulkan.org/spec/latest/chapters/resources.html#VUID-VkBufferCreateInfo-size-00912)
LoadTextureFromMemory: Invalid parameters

Note the 0 sizes for each texture, 0 channels, and the validation warnings of the creation of zero sized buffers.

It would appear that the code in:

std::future<bool> Renderer::LoadTextureFromMemoryAsync(const std::string& textureId,
                                                       const unsigned char* imageData,
                                                       int width,
                                                       int height,
                                                       int channels,
                                                       bool critical)

is creating jobs:

// Copy the source bytes so the caller can free/modify their buffer immediately
  size_t srcSize = static_cast<size_t>(width) * static_cast<size_t>(height) * static_cast<size_t>(channels);
  std::vector<unsigned char> dataCopy(srcSize);
  std::memcpy(dataCopy.data(), imageData, srcSize);

  textureTasksScheduled.fetch_add(1, std::memory_order_relaxed);
  uploadJobsTotal.fetch_add(1, std::memory_order_relaxed);
  auto task = [this, textureId, data = std::move(dataCopy), width, height, channels, critical]() mutable {
    PendingTextureJob job;
    job.type = PendingTextureJob::Type::FromMemory;
    job.priority = critical ? PendingTextureJob::Priority::Critical : PendingTextureJob::Priority::NonCritical;
    job.idOrPath = textureId;
      {
      std::lock_guard<std::mutex> lk(pendingTextureJobsMutex);
      pendingTextureJobs.emplace_back(std::move(job));
    }
    pendingTextureCv.notify_one();
    if (critical) {
      criticalJobsOutstanding.fetch_add(1, std::memory_order_relaxed);
    }
    textureTasksCompleted.fetch_add(1, std::memory_order_relaxed);
    return true;
  };

from my investigation, the width,height, channels and data parameters of these jobs are never populated.

the jobs appear to be processed in:

// Start background worker threads that drain pending texture jobs and perform GPU uploads
void Renderer::StartUploadsWorker(size_t workerCount) {

as

if (!memJobs.empty()) {
          try {
            // Process batched memory uploads with a single submit
            // Fallback to per-job if batching fails for any reason
            auto processSingle = [&](const PendingTextureJob& job) {
              (void) LoadTextureFromMemory(job.idOrPath,
                                           job.data.data(),
                                           job.width,
                                           job.height,
                                           job.channels);
              OnTextureUploaded(job.idOrPath);
              if (job.priority == PendingTextureJob::Priority::Critical) {
                criticalJobsOutstanding.fetch_sub(1, std::memory_order_relaxed);
              }
              uploadJobsCompleted.fetch_add(1, std::memory_order_relaxed);
            };

where the unpopulated fields are passed to LoadTextureFromMemory
This function subsequently checks the parameters, and aborts due to the zero parameters

// Load texture from raw image data in memory
bool Renderer::LoadTextureFromMemory(const std::string& textureId,
                                     const unsigned char* imageData,
                                     int width,
                                     int height,
                                     int channels) {
  ensureThreadLocalVulkanInit();
  const std::string resolvedId = ResolveTextureId(textureId);
  std::cout << "[LoadTextureFromMemory] start id=" << textureId << " -> resolved=" << resolvedId << " size=" << width << "x" << height << " ch=" << channels << std::endl;
  if (resolvedId.empty() || !imageData || width <= 0 || height <= 0 || channels <= 0) {
    std::cerr << "LoadTextureFromMemory: Invalid parameters" << std::endl;
    return false;
  }

Thus producing the console output I am seeing.

I tried populating the fields in the job from the task:

auto task = [this, textureId, data = std::move(dataCopy), width, height, channels, critical]() mutable {
    PendingTextureJob job;
    job.type = PendingTextureJob::Type::FromMemory;
    job.priority = critical ? PendingTextureJob::Priority::Critical : PendingTextureJob::Priority::NonCritical;
    job.idOrPath = textureId;
/*====================*/
      job.width = width;
      job.height = height;
      job.channels = channels;
      job.data = data; // note not sure whether this should be a copy or a move?
/*====================*/
      {
      std::lock_guard<std::mutex> lk(pendingTextureJobsMutex);
      pendingTextureJobs.emplace_back(std::move(job));
    }

Substituting my own very simple gltf model (a cube, camera and light source. The cube has a single KTX2 base colour texture applied) Without my 'hack' it was loading but like bistro was not rendering it's texture. Applying the change above results in the correct render of the model and texture.

With the change, the bistro scene spends a long time (~4 minutes in a release build) allocating memory for staging buffers. This is different behaviour than the unmodified code. It no longer displays the invalid parameter messages. instead we see many messages of the following form:

Created new memory block (pool type: 3)
Created new memory block (pool type: 3)
Created new memory block (pool type: 3)
Created new memory block (pool type: 3)
Created new memory block (pool type: 3)
Created new memory block (pool type: 3)
Created new memory block (pool type: 3)
Created new memory block (pool type: 3)

At the end of this time the app apparently hangs with a message

[AS] Requesting rebuild. Reason: uploads completed

The screen retains the loading texture progress message (indicating 1 short of the latest target 979/980). The application reports un-responsive if any attempt is made to interact with it (Debian Linux, Gnome display manager).

Any help or pointers appreciated!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions