Skip to content

Commit 91b44ab

Browse files
Radient: use data blobs for texture loading
Replace raw encoded and decoded texture data pointers and release callbacks with IRadientDataBlob.
1 parent 332ce80 commit 91b44ab

24 files changed

Lines changed: 1593 additions & 720 deletions

‎Radient/docs/DataBlobs.md‎

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,9 @@ trigger it.
129129
The callback receives an `IRadientDataBlob` and the shared `pUserData` context.
130130
For a mutable blob, it can query `IID_RadientMutableDataBlob` to attempt recycling.
131131
132-
The callback runs synchronously on the thread performing the final `EndRead`,
133-
outside the internal access lock. The blob stays alive throughout the callback,
134-
even if the callback releases the caller's reference. Retaining it afterward
132+
The callback runs synchronously on the thread performing the final `EndRead`
133+
and may call blob methods. The blob stays alive throughout the callback, even
134+
if the callback releases the caller's reference. Retaining it afterward
135135
requires acquiring a separate strong reference. A notification does not reserve
136136
access: another reader or writer may already be active. A mutable blob can be
137137
resized from the callback when no access scope is active. Recycling its contents
@@ -151,9 +151,8 @@ not receive a blob pointer because the blob is being destroyed and cannot be
151151
accessed or retained. It runs synchronously on the destroying thread and must not
152152
throw; C++ exceptions are caught and logged.
153153
154-
A reference blob can keep external storage alive by storing an owner reference
155-
in the callback context. The same pattern works for a parsed document that owns
156-
encoded images, avoiding a copy of those image bytes:
154+
A reference blob can keep external storage alive without copying its bytes by
155+
storing an owner reference in the callback context:
157156
158157
```cpp
159158
auto Bytes = std::make_shared<const std::vector<Uint8>>(std::initializer_list<Uint8>{1, 2, 3});
@@ -188,3 +187,67 @@ factory also takes the storage mode. A null descriptor returns
188187
`IRadientDataBlob_GetSize`, `IRadientDataBlob_BeginRead`, and
189188
`IRadientDataBlob_EndRead` for the base interface. The corresponding
190189
`IRadientMutableDataBlob_*` macros expose inherited reads, write access, and `Resize`.
190+
191+
## Encoded texture input
192+
193+
Set `RadientTextureLoadInfo::pDataBlob` to a non-empty blob containing the entire
194+
encoded image. `LoadTexture()` retains the blob and starts reading during the
195+
call without copying its bytes. End any write scope before submitting a mutable
196+
blob; an active writer causes `RADIENT_STATUS_INVALID_OPERATION` and no texture
197+
handle. The caller can release its blob reference after the call returns.
198+
199+
```cpp
200+
RadientTextureLoadInfo LoadInfo;
201+
LoadInfo.pDataBlob = Blob;
202+
LoadInfo.IsSRGB = True;
203+
RefCntAutoPtr<IRadientTextureAsset> Texture;
204+
const RADIENT_STATUS Status = AssetManager->LoadTexture(LoadInfo, &Texture);
205+
```
206+
207+
`LoadTexture()` retains read access for as long as it needs the blob's bytes.
208+
Writes and resizing are unavailable during that time.
209+
210+
`OnLastReaderReleased` can run during `LoadTexture()` or later on a worker thread.
211+
It marks the end of a read cycle, independently of GPU upload completion. A
212+
mutable blob can then be reused after acquiring write access; other readers
213+
still prevent writes and resizing. For REFERENCE blobs, external storage remains
214+
alive and unchanged for the blob's entire lifetime; release its owner through
215+
`OnDestroy`, as described above.
216+
217+
`pDataBlob` and the decoded-pixel descriptor `pTextureData` are mutually exclusive.
218+
219+
## Decoded texture input
220+
221+
`RadientTextureData::pDataBlob` contains mip 0 pixels beginning at byte zero.
222+
Set `Width`, `Height`, `Format`, and optionally `Stride`, then pass the descriptor
223+
through `RadientTextureLoadInfo::pTextureData`. A zero stride means tightly packed
224+
rows. An explicit stride can include row padding, which is preserved during loading.
225+
226+
The blob must cover `(Height - 1) * effective stride + active row size` bytes.
227+
Trailing padding after the final row is optional. Row padding and extra bytes
228+
do not affect texture caching. An undersized blob returns
229+
`RADIENT_STATUS_INVALID_ARGUMENT`. The read pointer must be aligned to the
230+
format's component size (1, 2, or 4 bytes). For multiple rows, the stride must
231+
also be a multiple of that size. Misaligned decoded storage returns
232+
`RADIENT_STATUS_INVALID_ARGUMENT`.
233+
234+
```cpp
235+
RadientTextureData Pixels;
236+
Pixels.Width = 2;
237+
Pixels.Height = 2;
238+
Pixels.Format = RADIENT_TEXTURE_FORMAT_RGBA8_UNORM;
239+
Pixels.pDataBlob = PixelBlob; // At least 16 bytes containing four RGBA pixels.
240+
241+
RadientTextureLoadInfo LoadInfo;
242+
LoadInfo.pTextureData = &Pixels;
243+
RefCntAutoPtr<IRadientTextureAsset> Texture;
244+
const RADIENT_STATUS Status = AssetManager->LoadTexture(LoadInfo, &Texture);
245+
```
246+
247+
`LoadTexture()` copies the descriptor, retains its blob, and reads the pixels
248+
without copying the input bytes. It generates the remaining mip levels.
249+
The caller can discard the descriptor and release its blob reference after the
250+
call returns. End any write scope before loading; writes and resizing remain
251+
unavailable while Radient is reading the blob. Read-only COPY, REFERENCE, and
252+
mutable blobs follow the same ownership and notification rules as encoded
253+
texture input.

‎Radient/include/Assets/RadientTextureSource.hpp‎

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,10 @@
2727
#pragma once
2828

2929
#include "RadientAssets.h"
30-
#include "RefCntAutoPtr.hpp"
30+
#include "Core/RadientDataBlobReadAccess.hpp"
3131

3232
#include <cstddef>
3333
#include <string>
34-
#include <vector>
3534

3635
namespace Diligent
3736
{
@@ -40,7 +39,7 @@ struct ITextureLoader;
4039
struct IRadientAssetLocation;
4140
struct IRadientAssetResolver;
4241

43-
/// Describes the valid readable span of RadientTextureData::pData.
42+
/// Describes the valid readable span of RadientTextureData::pDataBlob.
4443
struct RadientTextureDataSpan
4544
{
4645
/// Number of bytes in each row that contain texture data.
@@ -50,7 +49,7 @@ struct RadientTextureDataSpan
5049
/// Number of stored source rows. For block-compressed formats, this is the number of block rows.
5150
Uint32 RowCount = 0;
5251

53-
/// Minimum number of bytes that may be read from RadientTextureData::pData:
52+
/// Minimum number of bytes that may be read from RadientTextureData::pDataBlob:
5453
/// (RowCount - 1) * Stride + ActiveRowSize.
5554
/// The final row does not need padding bytes beyond ActiveRowSize.
5655
Uint64 DataSize = 0;
@@ -61,8 +60,9 @@ struct RadientTextureDataSpan
6160
/// \returns true if the format, dimensions, stride, and computed span are valid; false otherwise.
6261
///
6362
/// \remarks If RadientTextureData::Stride is zero, tightly packed rows are assumed.
64-
/// The function validates that non-zero stride is at least ActiveRowSize and that
65-
/// the computed DataSize does not overflow Uint64.
63+
/// Validates that non-zero stride is at least ActiveRowSize, that multiple rows
64+
/// start at component-aligned offsets, and that DataSize does not overflow Uint64.
65+
/// Does not acquire blob access or validate its data pointer or size.
6666
bool GetRadientTextureDataSpan(const RadientTextureData& TextureData,
6767
RadientTextureDataSpan& Span);
6868

@@ -121,13 +121,12 @@ class RadientTextureSource final
121121
return m_DataSize;
122122
}
123123

124-
bool OwnsMemory() const
124+
RADIENT_STATUS GetStatus() const
125125
{
126-
return !m_Data.empty() || m_ReleaseData != nullptr;
126+
return m_Status;
127127
}
128128

129-
void MakeMemoryCopy();
130-
129+
// For memory input, this source must outlive the returned loader.
131130
RADIENT_STATUS CreateLoader(IRadientAssetResolver* pAssetResolver,
132131
IRadientAssetLocation* pAssetLocation,
133132
ITextureLoader** ppLoader) const;
@@ -145,16 +144,16 @@ class RadientTextureSource final
145144
std::string m_BaseURI;
146145
Bool m_IsSRGB = False;
147146

148-
std::vector<Uint8> m_Data;
149-
const void* m_pData = nullptr;
150-
size_t m_DataSize = 0;
147+
RADIENT_STATUS m_Status = RADIENT_STATUS_OK;
148+
149+
// Retains the blob and read access while loaders use the source bytes.
150+
RadientDataBlobReadAccess m_ReadAccess;
151+
const void* m_pData = nullptr;
152+
size_t m_DataSize = 0;
151153

152154
RadientTextureData m_TextureData;
153155
Uint64 m_TextureDataActiveRowSize = 0;
154156
Uint32 m_TextureDataRowCount = 0;
155-
156-
RadientTextureReleaseDataCallbackType m_ReleaseData = nullptr;
157-
void* m_pReleaseDataUserData = nullptr;
158157
};
159158

160159
} // namespace Diligent

‎Radient/interface/RadientAssets.h‎

Lines changed: 53 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232
#include "RadientTypes.h"
3333
#include "RadientAssetResolver.h"
34+
#include "RadientDataBlob.h"
3435

3536
#include "../../../DiligentCore/Primitives/interface/Object.h"
3637

@@ -284,14 +285,6 @@ struct RadientMeshAssetDesc
284285
typedef struct RadientMeshAssetDesc RadientMeshAssetDesc;
285286

286287

287-
/// Texture load attributes.
288-
/// Optional callback used to release memory passed through RadientTextureLoadInfo::pData or
289-
/// RadientTextureLoadInfo::pTextureData->pData.
290-
/// The callback is invoked when Radient no longer needs the source memory.
291-
/// The callback may be invoked from any thread.
292-
/// The callback must not throw exceptions.
293-
typedef void (*RadientTextureReleaseDataCallbackType)(const void* pData, Uint64 DataSize, void* pUserData);
294-
295288
/// Texture format.
296289
DILIGENT_TYPED_ENUM(RADIENT_TEXTURE_FORMAT, Uint8){
297290
/// Unknown format.
@@ -381,27 +374,45 @@ DILIGENT_TYPED_ENUM(RADIENT_TEXTURE_FORMAT, Uint8){
381374
/// Four 32-bit floating-point components.
382375
RADIENT_TEXTURE_FORMAT_RGBA32_FLOAT};
383376

384-
/// Texture source data.
377+
/// Decoded mip 0 data for a 2D texture. The descriptor is copied by LoadTexture.
385378
struct RadientTextureData
386379
{
387-
/// Texture width in pixels.
380+
/// Texture width in pixels. Must be nonzero; defaults to zero.
388381
Uint32 Width DEFAULT_INITIALIZER(0);
389382

390-
/// Texture height in pixels.
383+
/// Texture height in pixels. Must be nonzero; defaults to zero.
391384
Uint32 Height DEFAULT_INITIALIZER(0);
392385

393-
/// Texture format.
386+
/// Pixel format. Must not be RADIENT_TEXTURE_FORMAT_UNKNOWN, which is the default.
394387
RADIENT_TEXTURE_FORMAT Format DEFAULT_INITIALIZER(RADIENT_TEXTURE_FORMAT_UNKNOWN);
395388

396-
/// Pointer to mip 0 pixel data.
397-
const void* pData DEFAULT_INITIALIZER(nullptr);
389+
/// Required blob containing mip 0 pixel data, starting at byte zero.
390+
/// Accepts read-only or mutable blobs. Its size must cover every active source row:
391+
/// (Height - 1) * effective stride + active row size. The effective stride is Stride
392+
/// when nonzero, otherwise the active row size derived from Format and Width.
393+
/// The final row does not require trailing padding; extra bytes are ignored.
394+
/// The read pointer must be aligned to the format's component size (1, 2, or 4 bytes).
395+
/// Insufficient size or component misalignment returns RADIENT_STATUS_INVALID_ARGUMENT.
396+
/// LoadTexture retains the blob and acquires read access before returning, then
397+
/// consumes the pixels directly without copying or repacking the source rows.
398+
/// Read access lasts while loading or upload preparation references these pixels.
399+
/// Other readers are allowed; writes and resizing remain blocked until all readers
400+
/// finish. An active writer causes LoadTexture to return RADIENT_STATUS_INVALID_OPERATION.
401+
/// The caller may release its blob reference after LoadTexture returns. For REFERENCE
402+
/// storage, the bytes remain alive and unchanged for the blob's entire lifetime;
403+
/// RadientDataBlobCreateInfo::OnDestroy can release their owner. Last-reader callbacks
404+
/// may run during LoadTexture or later on a worker thread; they do not indicate GPU
405+
/// upload completion. Defaults to nullptr, which is invalid for decoded texture input.
406+
IRadientDataBlob* pDataBlob DEFAULT_INITIALIZER(nullptr);
398407

399408
/// Row stride, in bytes. If zero, Radient derives tightly packed stride from Format and Width.
400-
/// Stride must be at least the active row size.
409+
/// Stride must be at least the active row size and, when Height is greater than one,
410+
/// a multiple of the format's component size, so each row remains component-aligned.
401411
Uint32 Stride DEFAULT_INITIALIZER(0);
402412
};
403413
typedef struct RadientTextureData RadientTextureData;
404414

415+
/// Texture load attributes. Selects encoded bytes, decoded pixels, or a URI source.
405416
struct RadientTextureLoadInfo
406417
{
407418
/// Source URI. For memory-backed textures, this is optional and may be used as the texture identity
@@ -412,31 +423,31 @@ struct RadientTextureLoadInfo
412423
/// value by the active asset resolver.
413424
const Char* BaseURI DEFAULT_INITIALIZER(nullptr);
414425

415-
/// Optional pointer to encoded texture data.
416-
const void* pData DEFAULT_INITIALIZER(nullptr);
417-
418-
/// Size of the encoded texture data, in bytes.
419-
Uint64 DataSize DEFAULT_INITIALIZER(0);
420-
421-
/// Optional pointer to texture data. Only 2D texture data is currently supported.
422-
/// Mip 0 data must be provided; Radient always generates mip levels.
426+
/// Optional blob containing the complete encoded texture, starting at byte zero.
427+
/// Accepts read-only or mutable blobs. The blob must be non-empty and its size must fit in size_t.
428+
/// Mutually exclusive with pTextureData. Radient retains the blob and acquires read access during
429+
/// LoadTexture(), before returning. The data pointer and size are checked within this read scope.
430+
/// The encoded bytes are consumed directly; LoadTexture() does not copy them.
431+
/// Read access remains active until Radient no longer needs the source bytes, including any
432+
/// decoder references used during upload preparation. The caller may release its blob reference
433+
/// after LoadTexture() returns. For REFERENCE storage, the caller keeps the referenced bytes alive
434+
/// and unchanged throughout the blob's lifetime; OnDestroy can release their owner. Other readers
435+
/// may access the blob while Radient is reading it. Mutable blobs cannot be written or resized
436+
/// until all readers finish. Finish any write access before calling LoadTexture(); an active writer
437+
/// causes the call to return RADIENT_STATUS_INVALID_OPERATION without creating a texture asset.
438+
/// If acquiring read access fails, no matching EndRead() or last-reader callback is performed.
439+
/// Once acquired, access is released on completion or failure. OnLastReaderReleased, if set,
440+
/// may run before LoadTexture() returns or later on a worker thread. This notification reports
441+
/// that all readers have finished; it does not indicate GPU upload completion or end the lifetime
442+
/// requirement for REFERENCE storage. See RadientDataBlobCreateInfo for callback details.
443+
IRadientDataBlob* pDataBlob DEFAULT_INITIALIZER(nullptr);
444+
445+
/// Optional pointer to decoded texture data, mutually exclusive with pDataBlob.
446+
/// Only 2D texture data is currently supported. Mip 0 data must be provided; Radient always
447+
/// generates mip levels. The descriptor is copied during LoadTexture(), and its pDataBlob
448+
/// is retained with read access while the pixels are needed; see RadientTextureData::pDataBlob.
423449
const RadientTextureData* pTextureData DEFAULT_INITIALIZER(nullptr);
424450

425-
/// Optional callback to release pData or pTextureData->pData when Radient no longer needs it.
426-
/// For pTextureData, DataSize is the minimum source span described by RadientTextureData::pData.
427-
/// If this callback is null and memory-backed source data is not null, Radient makes an internal copy of the data.
428-
/// If this callback is non-null and LoadTexture() validation accepts a memory-backed source, ownership
429-
/// of the source memory transfers to Radient before LoadTexture() returns. The callback is invoked exactly
430-
/// once even if a later loading step fails and LoadTexture() returns an error status. If LoadTexture()
431-
/// returns RADIENT_STATUS_INVALID_ARGUMENT during input validation, ownership remains with the caller and
432-
/// the callback is not invoked.
433-
/// The caller must not read, write, reuse, or release transferred source memory until the callback is invoked.
434-
/// The callback may be invoked from any thread and must not throw exceptions.
435-
RadientTextureReleaseDataCallbackType ReleaseData DEFAULT_INITIALIZER(nullptr);
436-
437-
/// User data passed to ReleaseData.
438-
void* pReleaseDataUserData DEFAULT_INITIALIZER(nullptr);
439-
440451
/// Interpret the texture as sRGB.
441452
Bool IsSRGB DEFAULT_INITIALIZER(False);
442453
};
@@ -669,9 +680,10 @@ DILIGENT_BEGIN_INTERFACE(IRadientAssetManager, IObject)
669680
/// The returned status reports source loading and GPU upload scheduling. A successful status
670681
/// does not guarantee that the texture is already available for sampling.
671682
/// Returns RADIENT_STATUS_PENDING when loading continues asynchronously.
672-
/// If LoadInfo.ReleaseData is non-null and input validation accepts a memory-backed source, ownership
673-
/// transfers to Radient before this method returns. The callback will be invoked exactly once even if
674-
/// this method returns a non-INVALID_ARGUMENT failure caused by a later admission or loading step.
683+
/// Encoded input in LoadInfo.pDataBlob or decoded input in LoadInfo.pTextureData->pDataBlob
684+
/// is retained with read access until the source bytes are no longer needed. An active writer
685+
/// prevents loading and returns RADIENT_STATUS_INVALID_OPERATION. The caller may release its
686+
/// blob reference after the call. Rejected loads leave ownership with the caller.
675687
VIRTUAL RADIENT_STATUS METHOD(LoadTexture)(THIS_
676688
const RadientTextureLoadInfo REF LoadInfo,
677689
IRadientTextureAsset** ppTexture) PURE;

‎Radient/src/Assets/RadientAssetManagerImpl.cpp‎

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@
3939
#include "GPUUploadManager.h"
4040
#include "ThreadPool.hpp"
4141

42-
#include <array>
4342
#include <atomic>
4443
#include <cstring>
4544
#include <exception>
@@ -254,21 +253,44 @@ RadientMaterialDefaultTextures CreateDefaultMaterialTextures(IThreadPool&
254253
RadientMaterialDefaultTextures DefaultTextures;
255254

256255
auto LoadDefaultTexture = [&](const char* URI, Uint32 Pixel, IRadientTextureAsset** ppTexture) {
257-
std::array<Uint32, DefaultTextureSize * DefaultTextureSize> Pixels;
258-
Pixels.fill(Pixel);
256+
RadientDataBlobCreateInfo BlobCI;
257+
BlobCI.Size = DefaultTextureSize * DefaultTextureSize * sizeof(Pixel);
258+
RefCntAutoPtr<IRadientMutableDataBlob> pPixels;
259+
RADIENT_STATUS Status = CreateRadientMutableDataBlob(BlobCI, &pPixels);
260+
if (Status != RADIENT_STATUS_OK)
261+
{
262+
LOG_ERROR_MESSAGE("Failed to allocate Radient default material texture '", URI, "'");
263+
return;
264+
}
265+
266+
void* pData = nullptr;
267+
Status = pPixels->BeginWrite(&pData);
268+
if (Status != RADIENT_STATUS_OK)
269+
{
270+
LOG_ERROR_MESSAGE("Failed to access Radient default material texture '", URI, "'");
271+
return;
272+
}
273+
for (Uint32 Index = 0; Index < DefaultTextureSize * DefaultTextureSize; ++Index)
274+
std::memcpy(static_cast<Uint8*>(pData) + Index * sizeof(Pixel), &Pixel, sizeof(Pixel));
275+
Status = pPixels->EndWrite();
276+
if (Status != RADIENT_STATUS_OK)
277+
{
278+
LOG_ERROR_MESSAGE("Failed to finish Radient default material texture '", URI, "'");
279+
return;
280+
}
259281

260282
RadientTextureData TextureData;
261-
TextureData.Width = DefaultTextureSize;
262-
TextureData.Height = DefaultTextureSize;
263-
TextureData.Format = RADIENT_TEXTURE_FORMAT_RGBA8_UNORM;
264-
TextureData.pData = Pixels.data();
265-
TextureData.Stride = DefaultTextureSize * sizeof(Pixel);
283+
TextureData.Width = DefaultTextureSize;
284+
TextureData.Height = DefaultTextureSize;
285+
TextureData.Format = RADIENT_TEXTURE_FORMAT_RGBA8_UNORM;
286+
TextureData.pDataBlob = pPixels;
287+
TextureData.Stride = DefaultTextureSize * sizeof(Pixel);
266288

267289
RadientTextureLoadInfo LoadInfo;
268290
LoadInfo.URI = URI;
269291
LoadInfo.pTextureData = &TextureData;
270292

271-
const RADIENT_STATUS Status = TextureManager.LoadTexture(ThreadPool, LoadInfo, ppTexture);
293+
Status = TextureManager.LoadTexture(ThreadPool, LoadInfo, ppTexture);
272294
if (RADIENT_FAILED(Status))
273295
LOG_ERROR_MESSAGE("Failed to create Radient default material texture '", URI, "'");
274296
};

0 commit comments

Comments
 (0)