c9ee1f7871
The issue was because the preset reload was only happened in CreateJobs but not ProcessJobs. But these two functions might be called from different AssetBuilder. The fix is to add preset reload for Processjobs too. There was another change to add debug device name for streaming image. Signed-off-by: Qing Tao <55564570+VickyAtAZ@users.noreply.github.com>
529 lines
22 KiB
C++
529 lines
22 KiB
C++
/*
|
|
* Copyright (c) Contributors to the Open 3D Engine Project.
|
|
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
*
|
|
*/
|
|
|
|
#include <Atom/RPI.Public/Image/ImageSystemInterface.h>
|
|
#include <Atom/RPI.Public/Image/StreamingImage.h>
|
|
#include <Atom/RPI.Public/Image/StreamingImagePool.h>
|
|
#include <Atom/RPI.Public/Image/StreamingImageController.h>
|
|
|
|
#include <Atom/RPI.Reflect/Image/ImageMipChainAssetCreator.h>
|
|
#include <Atom/RPI.Reflect/Image/StreamingImageAssetCreator.h>
|
|
|
|
#include <Atom/RHI/Factory.h>
|
|
|
|
#include <AtomCore/Instance/InstanceDatabase.h>
|
|
|
|
// Enable this define to debug output streaming image initialization and expanding process.
|
|
//#define AZ_RPI_STREAMING_IMAGE_DEBUG_LOG
|
|
|
|
AZ_DECLARE_BUDGET(RPI);
|
|
|
|
namespace AZ
|
|
{
|
|
namespace RPI
|
|
{
|
|
Data::Instance<StreamingImage> StreamingImage::FindOrCreate(const Data::Asset<StreamingImageAsset>& streamingImageAsset)
|
|
{
|
|
return Data::InstanceDatabase<StreamingImage>::Instance().FindOrCreate(
|
|
Data::InstanceId::CreateFromAssetId(streamingImageAsset.GetId()),
|
|
streamingImageAsset);
|
|
}
|
|
|
|
Data::Instance<StreamingImage> StreamingImage::CreateFromCpuData(
|
|
const StreamingImagePool& streamingImagePool,
|
|
RHI::ImageDimension imageDimension,
|
|
RHI::Size imageSize,
|
|
RHI::Format imageFormat,
|
|
const void* imageData,
|
|
size_t imageDataSize,
|
|
Uuid id)
|
|
{
|
|
Data::Instance<StreamingImage> existingImage = Data::InstanceDatabase<StreamingImage>::Instance().Find(Data::InstanceId::CreateFromAssetId(id));
|
|
AZ_Error("StreamingImage", !existingImage, "StreamingImage::CreateFromCpuData found an existing entry in the instance database for the provided id.");
|
|
|
|
RHI::ImageDescriptor imageDescriptor;
|
|
imageDescriptor.m_bindFlags = RHI::ImageBindFlags::ShaderRead;
|
|
imageDescriptor.m_dimension = imageDimension;
|
|
imageDescriptor.m_size = imageSize;
|
|
imageDescriptor.m_format = imageFormat;
|
|
|
|
const RHI::ImageSubresourceLayout imageSubresourceLayout = RHI::GetImageSubresourceLayout(imageDescriptor, RHI::ImageSubresource{});
|
|
|
|
const size_t expectedImageDataSize = imageSubresourceLayout.m_bytesPerImage * imageDescriptor.m_size.m_depth;
|
|
if (expectedImageDataSize != imageDataSize)
|
|
{
|
|
AZ_Error("StreamingImage", false, "StreamingImage::CreateFromCpuData expected '%d' bytes of image data, but got '%d' instead.", expectedImageDataSize, imageDataSize);
|
|
return nullptr;
|
|
}
|
|
|
|
Data::Asset<ImageMipChainAsset> mipChainAsset;
|
|
|
|
// Construct the mip chain asset.
|
|
{
|
|
ImageMipChainAssetCreator assetCreator;
|
|
assetCreator.Begin(Uuid::CreateRandom(), 1, 1);
|
|
assetCreator.BeginMip(imageSubresourceLayout);
|
|
assetCreator.AddSubImage(imageData, expectedImageDataSize);
|
|
assetCreator.EndMip();
|
|
if (!assetCreator.End(mipChainAsset))
|
|
{
|
|
AZ_Error("StreamingImage", false, "Failed to initialize mip chain asset");
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
Data::Asset<StreamingImageAsset> streamingImageAsset;
|
|
|
|
// Construct the streaming image asset.
|
|
{
|
|
StreamingImageAssetCreator assetCreator;
|
|
assetCreator.Begin(id);
|
|
assetCreator.SetImageDescriptor(imageDescriptor);
|
|
assetCreator.AddMipChainAsset(*mipChainAsset.Get());
|
|
assetCreator.SetFlags(StreamingImageFlags::NotStreamable);
|
|
assetCreator.SetPoolAssetId(streamingImagePool.GetAssetId());
|
|
if (!assetCreator.End(streamingImageAsset))
|
|
{
|
|
AZ_Error("StreamingImage", false, "Failed to initialize streaming image asset");
|
|
return nullptr;
|
|
}
|
|
}
|
|
|
|
return StreamingImage::FindOrCreate(streamingImageAsset);
|
|
}
|
|
|
|
Data::Instance<StreamingImage> StreamingImage::CreateInternal(StreamingImageAsset& streamingImageAsset)
|
|
{
|
|
Data::Instance<StreamingImage> streamingImage = aznew StreamingImage();
|
|
const RHI::ResultCode resultCode = streamingImage->Init(streamingImageAsset);
|
|
|
|
if (resultCode == RHI::ResultCode::Success)
|
|
{
|
|
return streamingImage;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
RHI::ResultCode StreamingImage::Init(StreamingImageAsset& imageAsset)
|
|
{
|
|
AZ_PROFILE_FUNCTION(RPI);
|
|
|
|
Data::Instance<StreamingImagePool> pool;
|
|
if (imageAsset.GetPoolAssetId().IsValid())
|
|
{
|
|
Data::Asset<RPI::StreamingImagePoolAsset> poolAsset(imageAsset.GetPoolAssetId(), AZ::AzTypeInfo<RPI::StreamingImagePoolAsset>::Uuid());
|
|
pool = StreamingImagePool::FindOrCreate(poolAsset);
|
|
}
|
|
else
|
|
{
|
|
pool = ImageSystemInterface::Get()->GetStreamingPool();
|
|
}
|
|
|
|
if (!pool)
|
|
{
|
|
AZ_Error("StreamingImage", false, "Failed to acquire the streaming image pool instance.");
|
|
return RHI::ResultCode::Fail;
|
|
}
|
|
|
|
// Cache off the RHI streaming image pool instance.
|
|
RHI::StreamingImagePool* rhiPool = pool->GetRHIPool();
|
|
|
|
/**
|
|
* NOTE: The tail mip-chain is required to exist as a dependency of this asset. This allows
|
|
* the image to initialize with well-defined content.
|
|
*/
|
|
const uint16_t mipChainTailIndex = static_cast<uint16_t>(imageAsset.GetMipChainCount() - 1);
|
|
|
|
RHI::ResultCode resultCode = RHI::ResultCode::Success;
|
|
|
|
const ImageMipChainAsset& mipChainTailAsset = imageAsset.GetTailMipChain();
|
|
|
|
{
|
|
RHI::StreamingImageInitRequest initRequest;
|
|
initRequest.m_image = GetRHIImage();
|
|
initRequest.m_descriptor = imageAsset.GetImageDescriptor();
|
|
initRequest.m_tailMipSlices = mipChainTailAsset.GetMipSlices();
|
|
|
|
// NOTE: Initialization can fail due to out-of-memory errors. Need to handle it at runtime.
|
|
resultCode = rhiPool->InitImage(initRequest);
|
|
}
|
|
|
|
if (resultCode == RHI::ResultCode::Success)
|
|
{
|
|
m_imageView = m_image->GetImageView(imageAsset.GetImageViewDescriptor());
|
|
if(!m_imageView.get())
|
|
{
|
|
AZ_Error("Image", false, "Failed to initialize RHI image view. This is not a recoverable error and is likely a bug.");
|
|
return RHI::ResultCode::Fail;
|
|
}
|
|
|
|
// Build a local set of mip chain asset handles.
|
|
for (size_t mipChainIndex = 0; mipChainIndex < imageAsset.GetMipChainCount(); ++mipChainIndex)
|
|
{
|
|
const Data::AssetId& assetId = imageAsset.GetMipChainAsset(mipChainIndex).GetId();
|
|
|
|
// We want to store off the id, not the AssetData instance. This simplifies the fetch / evict logic which
|
|
// can do more strict assertions.
|
|
m_mipChains.push_back(Data::Asset<ImageMipChainAsset>(assetId, azrtti_typeid<ImageMipChainAsset>()));
|
|
}
|
|
|
|
// Initialize the streaming state to have the tail mip active and ready.
|
|
m_state.m_residencyTarget = mipChainTailIndex;
|
|
m_state.m_streamingTarget = mipChainTailIndex;
|
|
|
|
// Setup masks for tail mip chain
|
|
const uint16_t mipChainBit = static_cast<uint16_t>(1 << mipChainTailIndex);
|
|
m_state.m_maskActive |= mipChainBit;
|
|
m_state.m_maskEvictable &= ~mipChainBit;
|
|
m_state.m_maskReady |= mipChainBit;
|
|
|
|
// Take references on dependent assets
|
|
m_imageAsset = { &imageAsset, AZ::Data::AssetLoadBehavior::PreLoad };
|
|
m_rhiPool = rhiPool;
|
|
m_pool = pool;
|
|
m_pool->AttachImage(this);
|
|
|
|
// Set rhi image name
|
|
m_image->SetName(Name(m_imageAsset.GetHint()));
|
|
|
|
#ifdef AZ_RPI_STREAMING_IMAGE_DEBUG_LOG
|
|
AZ_TracePrintf("StreamingImage", "Init image [%s]\n", m_image->GetName().data());
|
|
#endif
|
|
|
|
#if defined (AZ_RPI_STREAMING_IMAGE_HOT_RELOADING)
|
|
BusConnect(imageAsset.GetId());
|
|
#endif
|
|
return RHI::ResultCode::Success;
|
|
}
|
|
|
|
AZ_Warning("StreamingImagePool", false, "Failed to initialize RHI::Image on RHI::StreamingImagePool.");
|
|
return resultCode;
|
|
}
|
|
|
|
void StreamingImage::Shutdown()
|
|
{
|
|
if (IsInitialized())
|
|
{
|
|
#if defined (AZ_RPI_STREAMING_IMAGE_HOT_RELOADING)
|
|
Data::AssetBus::MultiHandler::BusDisconnect(GetAssetId());
|
|
#endif
|
|
|
|
if(m_pool)
|
|
{
|
|
m_pool->DetachImage(this);
|
|
m_pool = nullptr;
|
|
}
|
|
|
|
m_rhiPool = nullptr;
|
|
|
|
GetRHIImage()->Shutdown();
|
|
|
|
// Evict all active mip chains
|
|
for (size_t mipChainIndex = 0; mipChainIndex < m_mipChains.size(); ++mipChainIndex)
|
|
{
|
|
EvictMipChainAsset(mipChainIndex);
|
|
}
|
|
|
|
m_mipChains.clear();
|
|
m_state = {};
|
|
}
|
|
}
|
|
|
|
StreamingImage::~StreamingImage()
|
|
{
|
|
Shutdown();
|
|
}
|
|
|
|
void StreamingImage::SetTargetMip(uint16_t targetMipLevel)
|
|
{
|
|
if (m_streamingController)
|
|
{
|
|
m_streamingController->OnSetTargetMip(this, targetMipLevel);
|
|
}
|
|
}
|
|
|
|
uint16_t StreamingImage::GetResidentMipLevel()
|
|
{
|
|
return static_cast<uint16_t>(m_image->GetResidentMipLevel());
|
|
}
|
|
|
|
RHI::ResultCode StreamingImage::TrimToMipChainLevel(size_t mipChainIndex)
|
|
{
|
|
AZ_Assert(mipChainIndex < m_mipChains.size(), "Exceeded number of mip chains.");
|
|
|
|
const size_t mipChainBegin = m_state.m_streamingTarget;
|
|
const size_t mipChainEnd = mipChainIndex;
|
|
|
|
RHI::ResultCode resultCode = RHI::ResultCode::Success;
|
|
|
|
// We only evict if the current target is higher detail than our requested target.
|
|
if (mipChainBegin < mipChainEnd)
|
|
{
|
|
const uint32_t mipLevel = static_cast<uint32_t>(m_imageAsset->GetMipLevel(mipChainEnd));
|
|
resultCode = m_rhiPool->TrimImage(*m_image.get(), mipLevel);
|
|
|
|
// Start from the most detailed chain, evict all in-flight or loaded assets.
|
|
// Note: this should only happened after TrimImage which all the possible backend asset data referencing were removed
|
|
for (size_t chainIdx = mipChainBegin; chainIdx < mipChainEnd; ++chainIdx)
|
|
{
|
|
EvictMipChainAsset(chainIdx);
|
|
}
|
|
|
|
// Reset tracked state to match the new target.
|
|
m_state.m_residencyTarget = static_cast<uint16_t>(mipChainEnd);
|
|
m_state.m_streamingTarget = static_cast<uint16_t>(mipChainEnd);
|
|
}
|
|
|
|
return resultCode;
|
|
}
|
|
|
|
void StreamingImage::QueueExpandToMipChainLevel(size_t mipChainIndex)
|
|
{
|
|
AZ_Assert(IsStreamable(), "Only streamable StreamingImage's mip chain can be expanded");
|
|
AZ_Assert(mipChainIndex < m_mipChains.size(), "Exceeded number of mip chains.");
|
|
|
|
// Expand operation - queue streaming of mip chains up to target mip chain index.
|
|
if (m_state.m_streamingTarget > mipChainIndex)
|
|
{
|
|
// Start on the next-detailed chain from the streaming target.
|
|
const size_t mipChainBegin = m_state.m_streamingTarget - 1;
|
|
const size_t mipChainEnd = mipChainIndex - 1;
|
|
|
|
// Iterate through to the end chain and queue loading operations on the mip assets.
|
|
for (size_t i = mipChainBegin; i != mipChainEnd; --i)
|
|
{
|
|
FetchMipChainAsset(i);
|
|
}
|
|
|
|
m_state.m_streamingTarget = static_cast<uint16_t>(mipChainIndex);
|
|
}
|
|
}
|
|
|
|
void StreamingImage::QueueExpandToNextMipChainLevel()
|
|
{
|
|
// Return if already reach the end
|
|
if (m_state.m_streamingTarget == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
QueueExpandToMipChainLevel(m_state.m_streamingTarget - 1);
|
|
}
|
|
|
|
RHI::ResultCode StreamingImage::ExpandMipChain()
|
|
{
|
|
AZ_Assert(m_state.m_streamingTarget <= m_state.m_residencyTarget, "The target mip chain cannot be less detailed than the resident mip chain.")
|
|
|
|
RHI::ResultCode resultCode = RHI::ResultCode::Success;
|
|
|
|
if (m_state.m_streamingTarget < m_state.m_residencyTarget)
|
|
{
|
|
#ifdef AZ_RPI_STREAMING_IMAGE_DEBUG_LOG
|
|
AZ_TracePrintf("StreamingImage", "Expand image [%s]\n", GetImage()->GetName().data());
|
|
#endif
|
|
|
|
// Start by assuming we can expand residency to the full target range.
|
|
uint16_t mipChainIndexFound = m_state.m_streamingTarget;
|
|
|
|
// Walk the mip chains from most to least detailed, and track the latest instance
|
|
// of an unloaded mip chain. This incrementally shortens the interval.
|
|
for (uint16_t i = m_state.m_streamingTarget; i < m_state.m_residencyTarget; ++i)
|
|
{
|
|
// Can't expand to this chain, select the next one as the candidate.
|
|
if (!IsMipChainAssetReady(i))
|
|
{
|
|
mipChainIndexFound = i + 1;
|
|
}
|
|
}
|
|
|
|
// If we found a range of loaded mip chains, upload them from the low level mipchain to high level mipchain
|
|
// which the index should be from higher value to lower value
|
|
if (mipChainIndexFound != m_state.m_residencyTarget)
|
|
{
|
|
for (uint16_t mipChainIndex = m_state.m_residencyTarget-1;
|
|
mipChainIndex >= mipChainIndexFound && resultCode == RHI::ResultCode::Success;
|
|
mipChainIndex--)
|
|
{
|
|
resultCode = UploadMipChain(mipChainIndex);
|
|
if (mipChainIndex == 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
m_state.m_residencyTarget = mipChainIndexFound;
|
|
}
|
|
}
|
|
|
|
return resultCode;
|
|
}
|
|
|
|
void StreamingImage::EvictMipChainAsset(size_t mipChainIndex)
|
|
{
|
|
AZ_Assert(mipChainIndex < m_mipChains.size(), "Exceeded total number of mip chains.");
|
|
|
|
const uint16_t mipChainBit = static_cast<uint16_t>(1 << mipChainIndex);
|
|
|
|
const bool isMipChainActive = RHI::CheckBitsAll(m_state.m_maskActive, mipChainBit);
|
|
const bool isMipChainEvictable = RHI::CheckBitsAll(m_state.m_maskEvictable, mipChainBit);
|
|
|
|
if (isMipChainActive && isMipChainEvictable)
|
|
{
|
|
const uint32_t mipChainMask = ~mipChainBit;
|
|
m_state.m_maskActive &= mipChainMask;
|
|
m_state.m_maskReady &= mipChainMask;
|
|
|
|
Data::Asset<ImageMipChainAsset>& mipChainAsset = m_mipChains[mipChainIndex];
|
|
AZ_Assert(mipChainAsset.GetStatus() != Data::AssetData::AssetStatus::NotLoaded, "Asset marked as active, but mipChainAsset in 'NotLoaded' state.");
|
|
Data::AssetBus::MultiHandler::BusDisconnect(mipChainAsset.GetId());
|
|
mipChainAsset.Release();
|
|
}
|
|
}
|
|
|
|
void StreamingImage::FetchMipChainAsset(size_t mipChainIndex)
|
|
{
|
|
AZ_Assert(mipChainIndex < m_mipChains.size(), "Exceeded total number of mip chains.");
|
|
|
|
const uint16_t mipChainBit = static_cast<uint16_t>(1 << mipChainIndex);
|
|
|
|
const bool isMipChainActive = RHI::CheckBitsAll(m_state.m_maskActive, mipChainBit);
|
|
|
|
if (!isMipChainActive)
|
|
{
|
|
m_state.m_maskActive |= mipChainBit;
|
|
|
|
Data::Asset<ImageMipChainAsset>& mipChainAsset = m_mipChains[mipChainIndex];
|
|
AZ_Assert(mipChainAsset.Get() == nullptr, "Asset marked as inactive, but has a valid reference.");
|
|
|
|
// Connect to the AssetBus so we are ready to receive OnAssetReady(), which will call OnMipChainAssetReady().
|
|
// If the asset happens to already be loaded, OnAssetReady() will be called immediately.
|
|
Data::AssetBus::MultiHandler::BusConnect(mipChainAsset.GetId());
|
|
|
|
// And we request that the asset be loaded in case it isn't already.
|
|
mipChainAsset.QueueLoad();
|
|
|
|
#ifdef AZ_RPI_STREAMING_IMAGE_DEBUG_LOG
|
|
AZ_TracePrintf("StreamingImage", "Fetch mip chain asset [%s]\n", mipChainAsset.GetHint().c_str());
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
AZ_Assert(false, "FetchMipChainAsset called for a mip chain that was already active.");
|
|
}
|
|
}
|
|
|
|
bool StreamingImage::IsMipChainAssetReady(size_t mipChainIndex) const
|
|
{
|
|
AZ_Assert(mipChainIndex < m_mipChains.size(), "Exceeded total number of mip chains.");
|
|
|
|
return RHI::CheckBitsAny(m_state.m_maskReady, static_cast<uint16_t>(1 << mipChainIndex));
|
|
}
|
|
|
|
void StreamingImage::OnMipChainAssetReady(size_t mipChainIndex)
|
|
{
|
|
AZ_Assert(mipChainIndex < m_mipChains.size(), "Exceeded total number of mip chains.");
|
|
|
|
const uint16_t mipChainBit = static_cast<uint16_t>(1 << mipChainIndex);
|
|
|
|
AZ_Assert(RHI::CheckBitsAll(m_state.m_maskActive, mipChainBit), "Mip chain should be marked as active.");
|
|
|
|
m_state.m_maskReady |= mipChainBit;
|
|
|
|
if (m_streamingController)
|
|
{
|
|
m_streamingController->OnMipChainAssetReady(this);
|
|
}
|
|
}
|
|
|
|
RHI::ResultCode StreamingImage::UploadMipChain(size_t mipChainIndex)
|
|
{
|
|
if (const Data::Asset<ImageMipChainAsset>& mipChainAsset = m_mipChains[mipChainIndex])
|
|
{
|
|
const auto& mipSlices = mipChainAsset->GetMipSlices();
|
|
|
|
RHI::StreamingImageExpandRequest request;
|
|
request.m_image = GetRHIImage();
|
|
request.m_mipSlices = mipSlices;
|
|
|
|
request.m_completeCallback = [=]()
|
|
{
|
|
#ifdef AZ_RPI_STREAMING_IMAGE_DEBUG_LOG
|
|
AZ_TracePrintf("StreamingImage", "Upload mipchain done [%s]\n", mipChainAsset.GetHint().c_str());
|
|
#endif
|
|
EvictMipChainAsset(mipChainIndex);
|
|
};
|
|
|
|
#ifdef AZ_RPI_STREAMING_IMAGE_DEBUG_LOG
|
|
AZ_TracePrintf("StreamingImage", "Start Upload mipchain [%d] [%s] start [%d], resident [%d]\n", mipChainIndex,
|
|
mipChainAsset.GetHint().c_str(), m_image->GetResidentMipLevel());
|
|
#endif
|
|
|
|
return m_rhiPool->ExpandImage(request);
|
|
}
|
|
return RHI::ResultCode::InvalidOperation;
|
|
}
|
|
|
|
void StreamingImage::OnAssetReady(Data::Asset<Data::AssetData> asset)
|
|
{
|
|
size_t mipChainIndex = 0;
|
|
|
|
const size_t mipChainCount = m_mipChains.size();
|
|
|
|
for (; mipChainIndex < mipChainCount; ++mipChainIndex)
|
|
{
|
|
if (m_mipChains[mipChainIndex] == asset)
|
|
{
|
|
#ifdef AZ_RPI_STREAMING_IMAGE_DEBUG_LOG
|
|
AZ_TracePrintf("StreamingImage", "mip chain asset ready [%s]\n", asset.GetHint().c_str());
|
|
#endif
|
|
OnMipChainAssetReady(mipChainIndex);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void StreamingImage::OnAssetReloaded(Data::Asset<Data::AssetData> asset)
|
|
{
|
|
#if defined (AZ_RPI_STREAMING_IMAGE_HOT_RELOADING)
|
|
if (asset.GetId() == GetAssetId())
|
|
{
|
|
StreamingImageAsset* imageAsset = azrtti_cast<StreamingImageAsset*>(asset.GetData());
|
|
|
|
// Release the loaded mipchain assets from both current asset and new asset since they are coming from old asset
|
|
// This is due to we have to use PreLoad dependecy load behavior for streaming image asset
|
|
// before we can switch load behavior at runtime.
|
|
// [GFX TODO] [ATOM-14467] Remove unnecessary code in StreamingImage::OnAssetReloaded when runtime switching dependency load behavior is supported
|
|
m_imageAsset->ReleaseMipChainAssets();
|
|
imageAsset->ReleaseMipChainAssets();
|
|
|
|
// Re-initialize the image.
|
|
Shutdown();
|
|
[[maybe_unused]] RHI::ResultCode resultCode = Init(*imageAsset);
|
|
|
|
AZ_Assert(resultCode == RHI::ResultCode::Success, "Failed to re-initialize streaming image");
|
|
}
|
|
else
|
|
{
|
|
AZ_Assert(false, "The mip chain asset auto-reload was disabled. If you are sure you want to reload mip chain manually you can remove this assert");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
const Data::Instance<StreamingImagePool>& StreamingImage::GetPool() const
|
|
{
|
|
return m_pool;
|
|
}
|
|
|
|
bool StreamingImage::IsStreamable() const
|
|
{
|
|
return (RHI::CheckBitsAny(m_imageAsset->GetFlags(), StreamingImageFlags::NotStreamable) == false);
|
|
}
|
|
|
|
}
|
|
}
|