mirror of
https://github.com/overte-org/overte.git
synced 2025-08-10 21:43:13 +02:00
Update NetworkTexture to track current KTX download state
This commit is contained in:
parent
20f4d14e07
commit
11751611e1
6 changed files with 89 additions and 62 deletions
|
@ -252,7 +252,7 @@ ktx::KTXUniquePointer Texture::serialize(const Texture& texture) {
|
||||||
}
|
}
|
||||||
images.emplace_back(ktx::Image(imageOffset, (uint32_t)mip->getSize(), 0, cubeFaces));
|
images.emplace_back(ktx::Image(imageOffset, (uint32_t)mip->getSize(), 0, cubeFaces));
|
||||||
}
|
}
|
||||||
imageOffset += mip->getSize() + 4;
|
imageOffset += static_cast<uint32_t>(mip->getSize()) + 4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#include "KTX.h"
|
#include "KTX.h"
|
||||||
|
|
||||||
#include <algorithm> //min max and more
|
#include <algorithm> //min max and more
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
using namespace ktx;
|
using namespace ktx;
|
||||||
|
|
||||||
|
@ -90,6 +91,7 @@ size_t Header::evalPixelOrBlockSize() const {
|
||||||
return 4;
|
return 4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
qWarning() << "Unknown ktx format: " << glFormat << " " << glBaseInternalFormat << " " << glInternalFormat;
|
||||||
throw std::runtime_error("Unknown format");
|
throw std::runtime_error("Unknown format");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,7 +121,7 @@ ImageDescriptors Header::generateImageDescriptors() const {
|
||||||
ImageDescriptors descriptors;
|
ImageDescriptors descriptors;
|
||||||
|
|
||||||
uint32_t imageOffset = 0;
|
uint32_t imageOffset = 0;
|
||||||
for (auto level = 0; level < numberOfMipmapLevels; ++level) {
|
for (uint32_t level = 0; level < numberOfMipmapLevels; ++level) {
|
||||||
auto imageSize = static_cast<uint32_t>(evalImageSize(level));
|
auto imageSize = static_cast<uint32_t>(evalImageSize(level));
|
||||||
ImageHeader header {
|
ImageHeader header {
|
||||||
numberOfFaces == NUM_CUBEMAPFACES,
|
numberOfFaces == NUM_CUBEMAPFACES,
|
||||||
|
@ -131,7 +133,7 @@ ImageDescriptors Header::generateImageDescriptors() const {
|
||||||
imageOffset += imageSize + 4;
|
imageOffset += imageSize + 4;
|
||||||
|
|
||||||
ImageHeader::FaceOffsets offsets;
|
ImageHeader::FaceOffsets offsets;
|
||||||
for (auto i = 0; i < numberOfFaces; ++i) {
|
for (uint32_t i = 0; i < numberOfFaces; ++i) {
|
||||||
offsets.push_back(0);
|
offsets.push_back(0);
|
||||||
}
|
}
|
||||||
descriptors.push_back(ImageDescriptor(header, offsets));
|
descriptors.push_back(ImageDescriptor(header, offsets));
|
||||||
|
|
|
@ -38,6 +38,7 @@
|
||||||
#include <Finally.h>
|
#include <Finally.h>
|
||||||
#include <Profile.h>
|
#include <Profile.h>
|
||||||
|
|
||||||
|
#include "NetworkLogging.h"
|
||||||
#include "ModelNetworkingLogging.h"
|
#include "ModelNetworkingLogging.h"
|
||||||
#include <Trace.h>
|
#include <Trace.h>
|
||||||
#include <StatTracker.h>
|
#include <StatTracker.h>
|
||||||
|
@ -345,14 +346,12 @@ void NetworkTexture::makeRequest() {
|
||||||
// We special-handle ktx requests to run 2 concurrent requests right off the bat
|
// We special-handle ktx requests to run 2 concurrent requests right off the bat
|
||||||
PROFILE_ASYNC_BEGIN(resource, "Resource:" + getType(), QString::number(_requestID), { { "url", _url.toString() }, { "activeURL", _activeUrl.toString() } });
|
PROFILE_ASYNC_BEGIN(resource, "Resource:" + getType(), QString::number(_requestID), { { "url", _url.toString() }, { "activeURL", _activeUrl.toString() } });
|
||||||
|
|
||||||
if (!_ktxHeaderLoaded) {
|
if (!_ktxHeaderLoaded && !_highMipRequestFinished) {
|
||||||
qDebug() << ">>> Making request to " << _url << " for header";
|
qDebug() << ">>> Making request to " << _url << " for header";
|
||||||
_ktxHeaderRequest = ResourceManager::createResourceRequest(this, _activeUrl);
|
_ktxHeaderRequest = ResourceManager::createResourceRequest(this, _activeUrl);
|
||||||
|
|
||||||
if (!_ktxHeaderRequest) {
|
if (!_ktxHeaderRequest) {
|
||||||
//qCDebug(networking).noquote() << "Failed to get request for" << _url.toDisplayString();
|
//qCDebug(networking).noquote() << "Failed to get request for" << _url.toDisplayString();
|
||||||
ResourceCache::requestCompleted(_self);
|
|
||||||
finishedLoading(false);
|
|
||||||
PROFILE_ASYNC_END(resource, "Resource:" + getType(), QString::number(_requestID));
|
PROFILE_ASYNC_END(resource, "Resource:" + getType(), QString::number(_requestID));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -373,9 +372,14 @@ void NetworkTexture::makeRequest() {
|
||||||
_bytesReceived = _bytesTotal = _bytes = 0;
|
_bytesReceived = _bytesTotal = _bytes = 0;
|
||||||
|
|
||||||
_ktxHeaderRequest->send();
|
_ktxHeaderRequest->send();
|
||||||
|
|
||||||
|
startMipRangeRequest(NULL_MIP_LEVEL, NULL_MIP_LEVEL);
|
||||||
|
} else {
|
||||||
|
if (_lowestKnownPopulatedMip > 0) {
|
||||||
|
startMipRangeRequest(_lowestKnownPopulatedMip - 1, _lowestKnownPopulatedMip - 1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
startMipRangeRequest(NULL_MIP_LEVEL, NULL_MIP_LEVEL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkTexture::handleMipInterestCallback(uint16_t level) {
|
void NetworkTexture::handleMipInterestCallback(uint16_t level) {
|
||||||
|
@ -384,13 +388,24 @@ void NetworkTexture::handleMipInterestCallback(uint16_t level) {
|
||||||
|
|
||||||
void NetworkTexture::handleMipInterestLevel(int level) {
|
void NetworkTexture::handleMipInterestLevel(int level) {
|
||||||
_lowestRequestedMipLevel = std::min(static_cast<uint16_t>(level), _lowestRequestedMipLevel);
|
_lowestRequestedMipLevel = std::min(static_cast<uint16_t>(level), _lowestRequestedMipLevel);
|
||||||
if (!_ktxMipRequest) {
|
if (!_ktxMipRequest && _lowestKnownPopulatedMip > 0) {
|
||||||
startRequestForNextMipLevel();
|
//startRequestForNextMipLevel();
|
||||||
|
clearLoadPriority(this);
|
||||||
|
setLoadPriority(this, _lowestKnownPopulatedMip - 1);
|
||||||
|
ResourceCache::attemptRequest(_self);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetworkTexture::startRequestForNextMipLevel() {
|
void NetworkTexture::startRequestForNextMipLevel() {
|
||||||
startMipRangeRequest(std::max(0, _lowestKnownPopulatedMip - 1), std::max(0, _lowestKnownPopulatedMip - 1));
|
if (_lowestKnownPopulatedMip == 0) {
|
||||||
|
qWarning(networking) << "Requesting next mip level but all have been fulfilled";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_pending) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//startMipRangeRequest(std::max(0, _lowestKnownPopulatedMip - 1), std::max(0, _lowestKnownPopulatedMip - 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load mips in the range [low, high] (inclusive)
|
// Load mips in the range [low, high] (inclusive)
|
||||||
|
@ -406,7 +421,7 @@ void NetworkTexture::startMipRangeRequest(uint16_t low, uint16_t high) {
|
||||||
}
|
}
|
||||||
|
|
||||||
_ktxMipRequest = ResourceManager::createResourceRequest(this, _activeUrl);
|
_ktxMipRequest = ResourceManager::createResourceRequest(this, _activeUrl);
|
||||||
qDebug() << ">>> Making request to " << _url << " for " << low << " to " << high;
|
qDebug(networking) << ">>> Making request to " << _url << " for " << low << " to " << high;
|
||||||
|
|
||||||
_ktxMipLevelRangeInFlight = { low, high };
|
_ktxMipLevelRangeInFlight = { low, high };
|
||||||
if (isHighMipRequest) {
|
if (isHighMipRequest) {
|
||||||
|
@ -437,7 +452,7 @@ void NetworkTexture::ktxHeaderRequestFinished() {
|
||||||
if (_ktxHeaderRequest->getResult() == ResourceRequest::Success) {
|
if (_ktxHeaderRequest->getResult() == ResourceRequest::Success) {
|
||||||
_ktxHeaderLoaded = true;
|
_ktxHeaderLoaded = true;
|
||||||
_ktxHeaderData = _ktxHeaderRequest->getData();
|
_ktxHeaderData = _ktxHeaderRequest->getData();
|
||||||
maybeCreateKTX();
|
maybeHandleFinishedInitialLoad();
|
||||||
} else {
|
} else {
|
||||||
handleFailedRequest(_ktxHeaderRequest->getResult());
|
handleFailedRequest(_ktxHeaderRequest->getResult());
|
||||||
}
|
}
|
||||||
|
@ -455,15 +470,18 @@ void NetworkTexture::ktxMipRequestFinished() {
|
||||||
|
|
||||||
_lowestKnownPopulatedMip = _ktxMipLevelRangeInFlight.first;
|
_lowestKnownPopulatedMip = _ktxMipLevelRangeInFlight.first;
|
||||||
|
|
||||||
_textureSource->getGPUTexture()->assignStoredMip(_ktxMipLevelRangeInFlight.first,
|
auto texture = _textureSource->getGPUTexture();
|
||||||
_ktxMipRequest->getData().size(), reinterpret_cast<uint8_t*>(_ktxMipRequest->getData().data()));
|
if (!texture) {
|
||||||
//texture->assignStoredMip(level, image._imageSize, ktxData);
|
texture->assignStoredMip(_ktxMipLevelRangeInFlight.first,
|
||||||
|
_ktxMipRequest->getData().size(), reinterpret_cast<uint8_t*>(_ktxMipRequest->getData().data()));
|
||||||
|
} else {
|
||||||
|
qWarning(networking) << "Trying to update mips but texture is null";
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
_highMipRequestFinished = true;
|
_highMipRequestFinished = true;
|
||||||
_ktxHighMipData = _ktxMipRequest->getData();
|
_ktxHighMipData = _ktxMipRequest->getData();
|
||||||
maybeCreateKTX();
|
maybeHandleFinishedInitialLoad();
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
handleFailedRequest(_ktxMipRequest->getResult());
|
handleFailedRequest(_ktxMipRequest->getResult());
|
||||||
}
|
}
|
||||||
|
@ -472,11 +490,13 @@ void NetworkTexture::ktxMipRequestFinished() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// This is called when the header or top mips have been loaded
|
// This is called when the header or top mips have been loaded
|
||||||
void NetworkTexture::maybeCreateKTX() {
|
void NetworkTexture::maybeHandleFinishedInitialLoad() {
|
||||||
if (_headerRequestFinished && _highMipRequestFinished) {
|
if (_headerRequestFinished && _highMipRequestFinished) {
|
||||||
ResourceCache::requestCompleted(_self);
|
ResourceCache::requestCompleted(_self);
|
||||||
|
|
||||||
if (_ktxHeaderData.size() > 0 && _ktxHighMipData.size() > 0) {
|
if (_ktxHeaderData.size() == 0 || _ktxHighMipData.size() == 0) {
|
||||||
|
finishedLoading(false);
|
||||||
|
} else {
|
||||||
// create ktx...
|
// create ktx...
|
||||||
auto header = reinterpret_cast<const ktx::Header*>(_ktxHeaderData.data());
|
auto header = reinterpret_cast<const ktx::Header*>(_ktxHeaderData.data());
|
||||||
|
|
||||||
|
@ -490,6 +510,7 @@ void NetworkTexture::maybeCreateKTX() {
|
||||||
auto kvSize = header->bytesOfKeyValueData;
|
auto kvSize = header->bytesOfKeyValueData;
|
||||||
if (kvSize > _ktxHeaderData.size() - ktx::KTX_HEADER_SIZE) {
|
if (kvSize > _ktxHeaderData.size() - ktx::KTX_HEADER_SIZE) {
|
||||||
qWarning() << "Cannot load " << _url << ", did not receive all kv data with initial request";
|
qWarning() << "Cannot load " << _url << ", did not receive all kv data with initial request";
|
||||||
|
finishedLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -505,23 +526,23 @@ void NetworkTexture::maybeCreateKTX() {
|
||||||
std::string filename;
|
std::string filename;
|
||||||
if (found == keyValues.end()) {
|
if (found == keyValues.end()) {
|
||||||
qWarning("Source hash key not found, bailing");
|
qWarning("Source hash key not found, bailing");
|
||||||
filename = "test";
|
finishedLoading(false);
|
||||||
//return;
|
return;
|
||||||
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (found->_value.size() < 16) {
|
if (found->_value.size() < 32) {
|
||||||
filename = _activeUrl.fileName().toStdString();
|
qWarning("Invalid source hash key found, bailing");
|
||||||
}
|
finishedLoading(false);
|
||||||
else {
|
return;
|
||||||
|
} else {
|
||||||
filename = std::string(reinterpret_cast<char*>(found->_value.data()), 32);
|
filename = std::string(reinterpret_cast<char*>(found->_value.data()), 32);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto memKtx = ktx::KTX::createBare(*header, keyValues);
|
auto memKtx = ktx::KTX::createBare(*header, keyValues);
|
||||||
|
|
||||||
auto d = const_cast<uint8_t*>(memKtx->getStorage()->data());
|
//auto d = const_cast<uint8_t*>(memKtx->getStorage()->data());
|
||||||
///memcpy(d + memKtx->_storage->size() - _ktxHighMipData.size(), _ktxHighMipData.data(), _ktxHighMipData.size());
|
//memcpy(d + memKtx->_storage->size() - _ktxHighMipData.size(), _ktxHighMipData.data(), _ktxHighMipData.size());
|
||||||
|
|
||||||
auto textureCache = DependencyManager::get<TextureCache>();
|
auto textureCache = DependencyManager::get<TextureCache>();
|
||||||
|
|
||||||
|
@ -531,7 +552,8 @@ void NetworkTexture::maybeCreateKTX() {
|
||||||
KTXFilePointer file;
|
KTXFilePointer file;
|
||||||
auto& ktxCache = textureCache->_ktxCache;
|
auto& ktxCache = textureCache->_ktxCache;
|
||||||
if (!memKtx || !(file = ktxCache.writeFile(data, KTXCache::Metadata(filename, length)))) {
|
if (!memKtx || !(file = ktxCache.writeFile(data, KTXCache::Metadata(filename, length)))) {
|
||||||
qCWarning(modelnetworking) << _url << "file cache failed";
|
qCWarning(modelnetworking) << _url << " failed to write cache file";
|
||||||
|
finishedLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -581,36 +603,8 @@ void NetworkTexture::maybeCreateKTX() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ResourceCache::requestCompleted(_self);
|
|
||||||
|
|
||||||
setImage(texture, header->getPixelWidth(), header->getPixelHeight());
|
setImage(texture, header->getPixelWidth(), header->getPixelHeight());
|
||||||
|
|
||||||
return;
|
|
||||||
|
|
||||||
/*
|
|
||||||
// Force load the next two levels
|
|
||||||
{
|
|
||||||
QTimer* timer = new QTimer();
|
|
||||||
connect(timer, &QTimer::timeout, this, [=]() {
|
|
||||||
//startMipRangeRequest(level, level);
|
|
||||||
startRequestForNextMipLevel();
|
|
||||||
});
|
|
||||||
timer->setSingleShot(true);
|
|
||||||
timer->setInterval(4000);
|
|
||||||
timer->start();
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
QTimer* timer = new QTimer();
|
|
||||||
connect(timer, &QTimer::timeout, this, [=]() {
|
|
||||||
//startMipRangeRequest(level - 1, level - 1);
|
|
||||||
startRequestForNextMipLevel();
|
|
||||||
});
|
|
||||||
timer->setSingleShot(true);
|
|
||||||
timer->setInterval(6000);
|
|
||||||
timer->start();
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,7 +84,7 @@ protected:
|
||||||
void startRequestForNextMipLevel();
|
void startRequestForNextMipLevel();
|
||||||
|
|
||||||
void startMipRangeRequest(uint16_t low, uint16_t high);
|
void startMipRangeRequest(uint16_t low, uint16_t high);
|
||||||
void maybeCreateKTX();
|
void maybeHandleFinishedInitialLoad();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend class KTXReader;
|
friend class KTXReader;
|
||||||
|
@ -92,15 +92,28 @@ private:
|
||||||
|
|
||||||
image::TextureUsage::Type _type;
|
image::TextureUsage::Type _type;
|
||||||
|
|
||||||
KTXFilePointer _file;
|
|
||||||
static const uint16_t NULL_MIP_LEVEL;
|
static const uint16_t NULL_MIP_LEVEL;
|
||||||
|
struct KTXResourceState {
|
||||||
|
NOT_LOADED = 0,
|
||||||
|
LOADING_INITIAL_DATA, // Loading KTX Header + Low Resolution Mips
|
||||||
|
WAITING_FOR_MIP_REQUEST, // Waiting for the gpu layer to report that it needs higher resolution mips
|
||||||
|
PENDING_MIP_REQUEST, // We have added ourselves to the ResourceCache queue
|
||||||
|
REQUESTING_MIP // We have a mip in flight
|
||||||
|
};
|
||||||
|
|
||||||
|
KTXResourceState _ktxResourceState{ NOT_LOADED };
|
||||||
|
|
||||||
|
KTXFilePointer _file;
|
||||||
|
|
||||||
bool _sourceIsKTX { false };
|
bool _sourceIsKTX { false };
|
||||||
bool _ktxHeaderLoaded { false };
|
bool _ktxHeaderLoaded { false };
|
||||||
|
bool _highMipRequestFinished { false };
|
||||||
|
|
||||||
std::pair<uint16_t, uint16_t> _ktxMipLevelRangeInFlight{ NULL_MIP_LEVEL, NULL_MIP_LEVEL };
|
std::pair<uint16_t, uint16_t> _ktxMipLevelRangeInFlight{ NULL_MIP_LEVEL, NULL_MIP_LEVEL };
|
||||||
|
|
||||||
ResourceRequest* _ktxHeaderRequest { nullptr };
|
ResourceRequest* _ktxHeaderRequest { nullptr };
|
||||||
ResourceRequest* _ktxMipRequest { nullptr };
|
ResourceRequest* _ktxMipRequest { nullptr };
|
||||||
bool _headerRequestFinished{ false };
|
bool _headerRequestFinished { false };
|
||||||
bool _highMipRequestFinished{ false };
|
|
||||||
uint16_t _lowestRequestedMipLevel { NULL_MIP_LEVEL };
|
uint16_t _lowestRequestedMipLevel { NULL_MIP_LEVEL };
|
||||||
uint16_t _lowestKnownPopulatedMip { NULL_MIP_LEVEL };
|
uint16_t _lowestKnownPopulatedMip { NULL_MIP_LEVEL };
|
||||||
QByteArray _ktxHeaderData;
|
QByteArray _ktxHeaderData;
|
||||||
|
|
|
@ -474,8 +474,15 @@ int ResourceCache::getLoadingRequestCount() {
|
||||||
|
|
||||||
bool ResourceCache::attemptRequest(QSharedPointer<Resource> resource) {
|
bool ResourceCache::attemptRequest(QSharedPointer<Resource> resource) {
|
||||||
Q_ASSERT(!resource.isNull());
|
Q_ASSERT(!resource.isNull());
|
||||||
auto sharedItems = DependencyManager::get<ResourceCacheSharedItems>();
|
|
||||||
|
|
||||||
|
if (resource->_pending) {
|
||||||
|
qWarning(networking) << "Attempted to request " << resource->getURL() << " but it was already pending";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
resource->_pending = true;
|
||||||
|
|
||||||
|
auto sharedItems = DependencyManager::get<ResourceCacheSharedItems>();
|
||||||
if (_requestsActive >= _requestLimit) {
|
if (_requestsActive >= _requestLimit) {
|
||||||
// wait until a slot becomes available
|
// wait until a slot becomes available
|
||||||
sharedItems->appendPendingRequest(resource);
|
sharedItems->appendPendingRequest(resource);
|
||||||
|
@ -490,6 +497,12 @@ bool ResourceCache::attemptRequest(QSharedPointer<Resource> resource) {
|
||||||
|
|
||||||
void ResourceCache::requestCompleted(QWeakPointer<Resource> resource) {
|
void ResourceCache::requestCompleted(QWeakPointer<Resource> resource) {
|
||||||
auto sharedItems = DependencyManager::get<ResourceCacheSharedItems>();
|
auto sharedItems = DependencyManager::get<ResourceCacheSharedItems>();
|
||||||
|
|
||||||
|
auto sharedResource = resource.lock();
|
||||||
|
if (sharedResource) {
|
||||||
|
sharedResource->_pending = true;
|
||||||
|
}
|
||||||
|
|
||||||
sharedItems->removeRequest(resource);
|
sharedItems->removeRequest(resource);
|
||||||
--_requestsActive;
|
--_requestsActive;
|
||||||
|
|
||||||
|
|
|
@ -424,6 +424,9 @@ protected slots:
|
||||||
protected:
|
protected:
|
||||||
virtual void init();
|
virtual void init();
|
||||||
|
|
||||||
|
/// Called by ResourceCache to begin loading this Resource.
|
||||||
|
/// This method can be overriden to provide custom request functionality. If this is done,
|
||||||
|
/// downloadFinished and ResourceCache::requestCompleted must be called.
|
||||||
virtual void makeRequest();
|
virtual void makeRequest();
|
||||||
|
|
||||||
/// Checks whether the resource is cacheable.
|
/// Checks whether the resource is cacheable.
|
||||||
|
@ -461,6 +464,8 @@ protected:
|
||||||
int _requestID;
|
int _requestID;
|
||||||
ResourceRequest* _request{ nullptr };
|
ResourceRequest* _request{ nullptr };
|
||||||
|
|
||||||
|
bool _pending{ false };
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void handleDownloadProgress(uint64_t bytesReceived, uint64_t bytesTotal);
|
void handleDownloadProgress(uint64_t bytesReceived, uint64_t bytesTotal);
|
||||||
void handleReplyFinished();
|
void handleReplyFinished();
|
||||||
|
|
Loading…
Reference in a new issue