Fix VRAM leak on quality changes and RGBA16 motion vectors; more games
- Shim: reuse VRAM interop buffers across features instead of freeing them; ROCm 7.2.x's hipExternalMemoryGetMappedBuffer retains its view twice, so freed buffers never released their memory (~130 MB per quality change) - Bridge: close the exported fd after hipImportExternalMemory - Shim: accept R16G16B16A16_TYPELESS motion vectors and convert 4-channel motion vectors to RG16F on the GPU, keeping VRAM interop (Dying Light: The Beast rendered black) - Harness: D4R_HARNESS_RECREATE=N quality-change cycles, D4R_HARNESS_MV_RGBA16=1 motion vector format - Supported games: TLOU, Control, Cyberpunk 2077, Rift Apart, HZD Remastered, Subnautica 2, Alan Wake 2, Silent Hill 2, Dying Light: The Beast; general notes - README: GPU support section (native kernels only for gfx1101); ignore videos/
This commit is contained in:
1 parent
1952452830
commit
da263ecf75
6 files changed
+249
-28
No files matched your search
@@ -52,6 +52,35 @@ static ID3D12Fence* g_fence;
|
||||
static HANDLE g_event;
|
||||
static UINT64 g_fenceValue;
|
||||
|
||||
static unsigned long long process_vram_kib()
|
||||
{
|
||||
std::vector<unsigned long long> clients;
|
||||
unsigned long long total = 0;
|
||||
for (int fd = 0; fd < 4096; ++fd)
|
||||
{
|
||||
char path[64];
|
||||
std::snprintf(path, sizeof(path), "Z:\\proc\\self\\fdinfo\\%d", fd);
|
||||
FILE* file = std::fopen(path, "r");
|
||||
if (file == nullptr)
|
||||
continue;
|
||||
unsigned long long client = 0, vram = 0;
|
||||
bool drm = false;
|
||||
char line[256];
|
||||
while (std::fgets(line, sizeof(line), file) != nullptr)
|
||||
if (std::sscanf(line, "drm-client-id: %llu", &client) == 1)
|
||||
drm = true;
|
||||
else
|
||||
std::sscanf(line, "drm-memory-vram: %llu", &vram);
|
||||
std::fclose(file);
|
||||
if (drm && std::find(clients.begin(), clients.end(), client) == clients.end())
|
||||
{
|
||||
clients.push_back(client);
|
||||
total += vram;
|
||||
}
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
static bool check(HRESULT hr, const char* what)
|
||||
{
|
||||
if (FAILED(hr))
|
||||
@@ -487,7 +516,27 @@ int main(int argc, char** argv)
|
||||
ID3D12Resource* colorTexture = create_texture(resourceWidth, inHeight,
|
||||
rgba8 ? DXGI_FORMAT_R8G8B8A8_UNORM : DXGI_FORMAT_R16G16B16A16_FLOAT, D3D12_RESOURCE_FLAG_NONE);
|
||||
ID3D12Resource* depthTexture = create_texture(resourceWidth, inHeight, DXGI_FORMAT_R32_FLOAT, D3D12_RESOURCE_FLAG_NONE);
|
||||
ID3D12Resource* motionTexture = create_texture(motionWidth, motionHeight, DXGI_FORMAT_R16G16_FLOAT, D3D12_RESOURCE_FLAG_NONE);
|
||||
// D4R_HARNESS_MV_RGBA16=1: motion vectors in an R16G16B16A16_TYPELESS texture (as some games pass them), the
|
||||
// unused components filled with junk that must not reach DLSS.
|
||||
const bool mvRgba16 = std::getenv("D4R_HARNESS_MV_RGBA16") != nullptr && std::atoi(std::getenv("D4R_HARNESS_MV_RGBA16")) != 0;
|
||||
ID3D12Resource* motionTexture = create_texture(motionWidth, motionHeight,
|
||||
mvRgba16 ? DXGI_FORMAT_R16G16B16A16_TYPELESS : DXGI_FORMAT_R16G16_FLOAT, D3D12_RESOURCE_FLAG_NONE);
|
||||
std::vector<uint16_t> motionWide;
|
||||
auto motion_rows = [&](const uint16_t* rg) -> const void* {
|
||||
if (!mvRgba16)
|
||||
return rg;
|
||||
const size_t pixels = static_cast<size_t>(motionWidth) * motionHeight;
|
||||
motionWide.resize(pixels * 4);
|
||||
for (size_t pixel = 0; pixel < pixels; ++pixel)
|
||||
{
|
||||
motionWide[pixel * 4 + 0] = rg[pixel * 2 + 0];
|
||||
motionWide[pixel * 4 + 1] = rg[pixel * 2 + 1];
|
||||
motionWide[pixel * 4 + 2] = 0x5640; // 100.0
|
||||
motionWide[pixel * 4 + 3] = 0xd640; // -100.0
|
||||
}
|
||||
return motionWide.data();
|
||||
};
|
||||
const UINT motionRowBytes = motionWidth * (mvRgba16 ? 8 : 4);
|
||||
ID3D12Resource* exposureTexture = create_texture(1, 1,
|
||||
exposureRGBA32 ? DXGI_FORMAT_R32G32B32A32_FLOAT : DXGI_FORMAT_R32_FLOAT, D3D12_RESOURCE_FLAG_NONE);
|
||||
ID3D12Resource* outputTexture = create_texture(outWidth, outHeight,
|
||||
@@ -496,7 +545,7 @@ int main(int argc, char** argv)
|
||||
upload(colorTexture, rgba8 ? static_cast<const void*>(color8.data()) : static_cast<const void*>(color.data()),
|
||||
resourceWidth * (rgba8 ? 4 : 8), srv);
|
||||
upload(depthTexture, depth.data(), resourceWidth * 4, srv);
|
||||
upload(motionTexture, motion.data(), motionWidth * 4, srv);
|
||||
upload(motionTexture, motion_rows(motion.data()), motionRowBytes, srv);
|
||||
upload(exposureTexture, exposureRGBA32 ? static_cast<const void*>(exposureRGBA.data())
|
||||
: static_cast<const void*>(&exposureValue),
|
||||
exposureRGBA32 ? 16 : 4, srv);
|
||||
@@ -662,7 +711,7 @@ int main(int argc, char** argv)
|
||||
motion[pixel * 2 + 0] = mvX;
|
||||
motion[pixel * 2 + 1] = mvY;
|
||||
}
|
||||
update_texture(motionTexture, motion.data(), motionWidth * 4, srv);
|
||||
update_texture(motionTexture, motion_rows(motion.data()), motionRowBytes, srv);
|
||||
}
|
||||
d4r_ngx_set_float(parameters, "Jitter.Offset.X", signX * jx);
|
||||
d4r_ngx_set_float(parameters, "Jitter.Offset.Y", signY * jy);
|
||||
@@ -700,7 +749,7 @@ int main(int argc, char** argv)
|
||||
}
|
||||
update_texture(colorTexture, color.data(), resourceWidth * 8, srv);
|
||||
update_texture(depthTexture, depth.data(), resourceWidth * 4, srv);
|
||||
update_texture(motionTexture, motion.data(), motionWidth * 4, srv);
|
||||
update_texture(motionTexture, motion_rows(motion.data()), motionRowBytes, srv);
|
||||
}
|
||||
if (replayDir != nullptr)
|
||||
{
|
||||
@@ -711,7 +760,7 @@ int main(int argc, char** argv)
|
||||
return 1;
|
||||
update_texture(colorTexture, replay.color.data(), resourceWidth * 8, srv);
|
||||
update_texture(depthTexture, replay.depth.data(), resourceWidth * 4, srv);
|
||||
update_texture(motionTexture, replay.motion.data(), motionWidth * 4, srv);
|
||||
update_texture(motionTexture, motion_rows(reinterpret_cast<const uint16_t*>(replay.motion.data())), motionRowBytes, srv);
|
||||
d4r_ngx_set_float(parameters, "Jitter.Offset.X", replay.jitterX);
|
||||
d4r_ngx_set_float(parameters, "Jitter.Offset.Y", replay.jitterY);
|
||||
d4r_ngx_set_float(parameters, "MV.Scale.X", replay.mvScaleX);
|
||||
@@ -836,6 +885,50 @@ int main(int argc, char** argv)
|
||||
nonzero += value != 0;
|
||||
std::printf("output read back: %zu of %zu bytes nonzero, written to %s\n", nonzero, output.size(), argv[2]);
|
||||
|
||||
// D4R_HARNESS_RECREATE=N: N more release/create cycles at alternating render sizes (as when a game's DLSS
|
||||
// quality setting changes), a few evaluations each, logging this process's VRAM to find leaks per cycle.
|
||||
const int recreateCycles = std::getenv("D4R_HARNESS_RECREATE") != nullptr ? std::atoi(std::getenv("D4R_HARNESS_RECREATE")) : 0;
|
||||
for (int cycle = 1; cycle <= recreateCycles; ++cycle)
|
||||
{
|
||||
release(feature);
|
||||
feature = nullptr;
|
||||
const UINT width = cycle % 2 != 0 ? inWidth * 10 / 13 : inWidth;
|
||||
const UINT height = cycle % 2 != 0 ? inHeight * 10 / 13 : inHeight;
|
||||
d4r_ngx_set_uint(parameters, "Width", width);
|
||||
d4r_ngx_set_uint(parameters, "Height", height);
|
||||
d4r_ngx_set_uint(parameters, "DLSS.Render.Subrect.Dimensions.Width", width);
|
||||
d4r_ngx_set_uint(parameters, "DLSS.Render.Subrect.Dimensions.Height", height);
|
||||
result = createFeature(g_list, 1, parameters, &feature);
|
||||
if (result != NGX_SUCCESS)
|
||||
{
|
||||
std::printf("RECREATE cycle %d: CreateFeature -> 0x%08x\n", cycle, result);
|
||||
return 1;
|
||||
}
|
||||
for (int frame = 0; frame < 4; ++frame)
|
||||
{
|
||||
d4r_ngx_set_int(parameters, "Reset", frame == 0 ? 1 : 0);
|
||||
evaluate(g_list, feature, parameters, nullptr);
|
||||
submit_and_wait();
|
||||
Sleep(frameWaitMs);
|
||||
}
|
||||
const std::vector<uint8_t> cycleOutput = read_back(outputTexture, D3D12_RESOURCE_STATE_UNORDERED_ACCESS,
|
||||
outWidth * (rgba8 ? 4 : 8));
|
||||
if (FILE* cycleFile = std::fopen((std::string(argv[2]) + ".cycle" + std::to_string(cycle)).c_str(), "wb"))
|
||||
{
|
||||
std::fwrite(cycleOutput.data(), 1, cycleOutput.size(), cycleFile);
|
||||
std::fclose(cycleFile);
|
||||
}
|
||||
// Also to the quality report file, which survives when Proton drops stdout.
|
||||
const char* reportPath = std::getenv("D4R_HARNESS_QUALITY_SCENE");
|
||||
FILE* report = reportPath != nullptr ? std::fopen(reportPath, "a") : nullptr;
|
||||
for (FILE* out : {stdout, report})
|
||||
if (out != nullptr)
|
||||
std::fprintf(out, "RECREATE cycle %d (%ux%u): process VRAM %llu KiB\n", cycle, width, height,
|
||||
process_vram_kib());
|
||||
if (report != nullptr)
|
||||
std::fclose(report);
|
||||
}
|
||||
|
||||
release(feature);
|
||||
shutdown();
|
||||
return 0;
|
||||
|
||||
+92
-10
@@ -836,7 +836,8 @@ static bool supported_input(Plane plane, DXGI_FORMAT format)
|
||||
case Plane::Motion:
|
||||
return format == DXGI_FORMAT_R16G16_FLOAT || format == DXGI_FORMAT_R16G16_TYPELESS ||
|
||||
format == DXGI_FORMAT_R32G32_FLOAT || format == DXGI_FORMAT_R32G32_TYPELESS ||
|
||||
format == DXGI_FORMAT_R16G16B16A16_FLOAT || format == DXGI_FORMAT_R32G32B32A32_FLOAT;
|
||||
format == DXGI_FORMAT_R16G16B16A16_FLOAT || format == DXGI_FORMAT_R16G16B16A16_TYPELESS ||
|
||||
format == DXGI_FORMAT_R32G32B32A32_FLOAT;
|
||||
case Plane::Exposure:
|
||||
return format == DXGI_FORMAT_R32_FLOAT || format == DXGI_FORMAT_R32_TYPELESS ||
|
||||
format == DXGI_FORMAT_R16_FLOAT || format == DXGI_FORMAT_R16_TYPELESS ||
|
||||
@@ -959,6 +960,7 @@ static void convert_row_in(Plane plane, DXGI_FORMAT format, const uint8_t* sourc
|
||||
std::memcpy(half + x * 2, source + x * 4, 4);
|
||||
break;
|
||||
case DXGI_FORMAT_R16G16B16A16_FLOAT:
|
||||
case DXGI_FORMAT_R16G16B16A16_TYPELESS:
|
||||
std::memcpy(half + x * 2, source + x * 8, 4);
|
||||
break;
|
||||
case DXGI_FORMAT_R32G32B32A32_FLOAT:
|
||||
@@ -1556,6 +1558,7 @@ struct Feature
|
||||
// formats later stop qualifying.
|
||||
bool vram = false, vramDecided = false;
|
||||
VramImage colorConversion;
|
||||
VramImage motionConversion;
|
||||
VramImage exposureConversion;
|
||||
VramImage outputConversion;
|
||||
// Split frames: the rest of frame N's command list is submitted only once
|
||||
@@ -2159,9 +2162,33 @@ static int device_local_type(uint32_t typeBits)
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Buffers of released features, reused by later ones. ROCm up to at least 7.2.4 never frees memory mapped with
|
||||
// hipExternalMemoryGetMappedBuffer (the mapping's buffer view is retained twice but released once by hipFree), so
|
||||
// destroying a buffer would leak its memory: each DLSS quality change (a new feature) would add ~100 MB of VRAM.
|
||||
static std::mutex g_vramPoolMutex;
|
||||
static std::vector<VramBuffer> g_vramPool;
|
||||
constexpr size_t kVramPoolLimit = 64;
|
||||
|
||||
// The smallest pooled buffer that holds `bytes`.
|
||||
static bool take_pooled_vram_buffer(VramBuffer& target, size_t bytes)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_vramPoolMutex);
|
||||
auto best = g_vramPool.end();
|
||||
for (auto it = g_vramPool.begin(); it != g_vramPool.end(); ++it)
|
||||
if (it->bytes >= bytes && (best == g_vramPool.end() || it->bytes < best->bytes))
|
||||
best = it;
|
||||
if (best == g_vramPool.end())
|
||||
return false;
|
||||
target = *best;
|
||||
g_vramPool.erase(best);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Game thread. The CUDA import runs on the worker, which owns the context.
|
||||
static bool create_vram_buffer(VramBuffer& target, size_t bytes)
|
||||
{
|
||||
if (take_pooled_vram_buffer(target, bytes))
|
||||
return true;
|
||||
VramBuffer buffer;
|
||||
VkExternalMemoryBufferCreateInfo external = {VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO};
|
||||
external.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT;
|
||||
@@ -2212,8 +2239,11 @@ static void destroy_vram_buffer(VramBuffer& buffer)
|
||||
{
|
||||
if (buffer.external != nullptr)
|
||||
g.worker.call([&buffer] {
|
||||
g.cu.memFree(buffer.device);
|
||||
return g_vk.release(buffer.external);
|
||||
const int freed = g.cu.memFree(buffer.device);
|
||||
const int released = g_vk.release(buffer.external);
|
||||
if (freed != 0 || released != 0)
|
||||
logf("VRAM interop: freeing %zu-byte buffer: cuMemFree %d, release %d", buffer.bytes, freed, released);
|
||||
return released;
|
||||
});
|
||||
if (buffer.memory != VK_NULL_HANDLE)
|
||||
g_vk.free(g_vk.device, buffer.memory, nullptr);
|
||||
@@ -2222,6 +2252,34 @@ static void destroy_vram_buffer(VramBuffer& buffer)
|
||||
buffer = VramBuffer{};
|
||||
}
|
||||
|
||||
// Only once no command list or CUDA work uses the buffer: keeps it for a later feature (see g_vramPool).
|
||||
static void recycle_vram_buffer(VramBuffer& buffer)
|
||||
{
|
||||
if (buffer.buffer == VK_NULL_HANDLE)
|
||||
return;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_vramPoolMutex);
|
||||
if (g_vramPool.size() < kVramPoolLimit)
|
||||
{
|
||||
g_vramPool.push_back(buffer);
|
||||
buffer = VramBuffer{};
|
||||
return;
|
||||
}
|
||||
}
|
||||
destroy_vram_buffer(buffer);
|
||||
}
|
||||
|
||||
static void free_vram_pool()
|
||||
{
|
||||
std::vector<VramBuffer> pool;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_vramPoolMutex);
|
||||
pool.swap(g_vramPool);
|
||||
}
|
||||
for (VramBuffer& buffer : pool)
|
||||
destroy_vram_buffer(buffer);
|
||||
}
|
||||
|
||||
static void destroy_vram_image(VramImage& image)
|
||||
{
|
||||
if (image.memory != VK_NULL_HANDLE)
|
||||
@@ -2349,7 +2407,21 @@ static bool vram_exposure_blit_supported(VkFormat format)
|
||||
}
|
||||
}
|
||||
|
||||
// Whether `resource` can be copied raw (or, for colour/exposure, blitted)
|
||||
// Motion vectors in a wider float format keep their first two components (as the host path does).
|
||||
static bool vram_motion_blit_supported(VkFormat format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case VK_FORMAT_R16G16B16A16_SFLOAT:
|
||||
case VK_FORMAT_R32G32_SFLOAT:
|
||||
case VK_FORMAT_R32G32B32A32_SFLOAT:
|
||||
return vram_blit_supported(format, VK_FORMAT_R16G16_SFLOAT);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Whether `resource` can be copied raw (or, for colour/motion/exposure, blitted)
|
||||
// to/from the canonical layout of `plane`.
|
||||
static bool describe_vram_copy(ID3D12Resource* resource, Plane plane, VramCopy& copy, bool output = false)
|
||||
{
|
||||
@@ -2374,6 +2446,8 @@ static bool describe_vram_copy(ID3D12Resource* resource, Plane plane, VramCopy&
|
||||
}
|
||||
if (plane == Plane::Exposure)
|
||||
copy.convert = vram_exposure_blit_supported(format);
|
||||
else if (plane == Plane::Motion)
|
||||
copy.convert = vram_motion_blit_supported(format);
|
||||
return copy.convert || vk_texel_bytes(format) == canonical_texel_bytes(plane);
|
||||
}
|
||||
|
||||
@@ -2415,9 +2489,10 @@ static bool record_vram_inputs(Feature& feature, ID3D12GraphicsCommandList* list
|
||||
g_vk.copyImageToBuffer(cmd, copy.image, layout, slot.vram[index].buffer, 1, ®ion);
|
||||
continue;
|
||||
}
|
||||
// Convert colour to RGBA16F or exposure's first component to R32F,
|
||||
// then copy from the canonical image into the shared buffer.
|
||||
// Convert colour to RGBA16F, motion to RG16F or exposure's first component
|
||||
// to R32F, then copy from the canonical image into the shared buffer.
|
||||
VkImage conversion = kPlanes[index] == Plane::Exposure ? feature.exposureConversion.image
|
||||
: kPlanes[index] == Plane::Motion ? feature.motionConversion.image
|
||||
: feature.colorConversion.image;
|
||||
VkImageMemoryBarrier toDestination = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
|
||||
toDestination.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
||||
@@ -2633,14 +2708,15 @@ static void release_vram(Feature& feature)
|
||||
g_vk.destroySemaphore(g_vk.device, feature.splitSemaphore, nullptr);
|
||||
for (InputSlot& slot : feature.inputs)
|
||||
for (VramBuffer& buffer : slot.vram)
|
||||
destroy_vram_buffer(buffer);
|
||||
recycle_vram_buffer(buffer);
|
||||
for (OutputSlot& slot : feature.outputs)
|
||||
destroy_vram_buffer(slot.vram);
|
||||
destroy_vram_buffer(feature.gpuMarker);
|
||||
recycle_vram_buffer(slot.vram);
|
||||
recycle_vram_buffer(feature.gpuMarker);
|
||||
for (VramBuffer& buffer : feature.retiredBuffers)
|
||||
destroy_vram_buffer(buffer);
|
||||
recycle_vram_buffer(buffer);
|
||||
feature.retiredBuffers.clear();
|
||||
destroy_vram_image(feature.colorConversion);
|
||||
destroy_vram_image(feature.motionConversion);
|
||||
destroy_vram_image(feature.exposureConversion);
|
||||
destroy_vram_image(feature.outputConversion);
|
||||
for (VramImage& image : feature.retiredImages)
|
||||
@@ -4437,6 +4513,10 @@ static NgxResult evaluate(ID3D12GraphicsCommandList* list, const NgxHandle* hand
|
||||
vramInputs[0].width, vramInputs[0].height,
|
||||
VK_FORMAT_R16G16B16A16_SFLOAT))
|
||||
return NGX_FAIL_PLATFORM_ERROR;
|
||||
if (vramInputs[2].convert && !ensure_conversion_image(*feature, feature->motionConversion,
|
||||
vramInputs[2].width, vramInputs[2].height,
|
||||
VK_FORMAT_R16G16_SFLOAT))
|
||||
return NGX_FAIL_PLATFORM_ERROR;
|
||||
if (inputCount == 4 && vramInputs[3].convert &&
|
||||
!ensure_conversion_image(*feature, feature->exposureConversion,
|
||||
vramInputs[3].width, vramInputs[3].height, VK_FORMAT_R32_SFLOAT))
|
||||
@@ -4688,6 +4768,8 @@ D4R_EXPORT NgxResult NVSDK_NGX_D3D12_Shutdown()
|
||||
}
|
||||
for (Feature* feature : features)
|
||||
release_feature(feature);
|
||||
if (g_vk.ready)
|
||||
free_vram_pool();
|
||||
std::lock_guard<std::mutex> lock(g.mutex);
|
||||
if (g.ngxInitialized)
|
||||
{
|
||||
|
||||
@@ -2589,8 +2589,10 @@ CUresult WINAPI cuGetProcAddress(const char* symbol, void** pointer, int cuda_ve
|
||||
handles (Wine 11 layout: a VkDevice client object holds the unix object
|
||||
pointer at offset 8; a non-dispatchable client handle is the unix object
|
||||
pointer; every unix object starts with its host handle), the host fd is
|
||||
exported with vkGetMemoryFdKHR and imported into HIP, which takes ownership
|
||||
of the fd. */
|
||||
exported with vkGetMemoryFdKHR and imported into HIP. HIP does not take
|
||||
ownership of the fd (hipDestroyExternalMemory leaves it open, and an open fd
|
||||
keeps the memory alive after vkFreeMemory), so it is closed once imported, as
|
||||
ROCm's own GL interop does after mapping. */
|
||||
typedef struct
|
||||
{
|
||||
int type; /* hipExternalMemoryHandleTypeOpaqueFd = 1 */
|
||||
@@ -2697,11 +2699,11 @@ CUresult WINAPI d4rImportVulkanMemory(void* client_device, uint64_t client_memor
|
||||
desc.flags = 1; /* dedicated allocation */
|
||||
void* external = NULL;
|
||||
int result = import(&external, &desc);
|
||||
close(fd);
|
||||
if (result != 0)
|
||||
{
|
||||
tracef("d4rImportVulkanMemory: hipImportExternalMemory(fd=%d, %llu bytes) failed: %d", fd,
|
||||
(unsigned long long)bytes, result);
|
||||
close(fd);
|
||||
return CUDA_ERROR_INVALID_VALUE;
|
||||
}
|
||||
D4rHipExternalMemoryBufferDesc buffer;
|
||||
@@ -2835,5 +2837,7 @@ CUresult WINAPI d4rSetArrayRedirect(CUarray array, CUdeviceptr pointer, uint32_t
|
||||
CUresult WINAPI d4rReleaseVulkanMemory(void* memory)
|
||||
{
|
||||
HIP_DESTROY_EXTERNAL_MEMORY_FN destroy = (HIP_DESTROY_EXTERNAL_MEMORY_FN)hip_symbol("hipDestroyExternalMemory");
|
||||
return destroy != NULL && destroy(memory) == 0 ? CUDA_SUCCESS : CUDA_ERROR_INVALID_VALUE;
|
||||
const int result = destroy != NULL ? destroy(memory) : -1;
|
||||
tracef("d4rReleaseVulkanMemory: hipDestroyExternalMemory(%p) -> %d", memory, result);
|
||||
return result == 0 ? CUDA_SUCCESS : CUDA_ERROR_INVALID_VALUE;
|
||||
}
|
||||
Reference in new issue
Block a user