Browse Source

Following Initial Impl for Android Native Hardware Decode

pull/2977/head
CamilleLaVey 3 months ago
parent
commit
ca4c26a4b4
  1. 55
      src/android/app/src/main/java/org/yuzu/yuzu_emu/media/NativeMediaCodec.java
  2. 1
      src/video_core/CMakeLists.txt
  3. 18
      src/video_core/host1x/ffmpeg/mediacodec_bridge.h
  4. 83
      src/video_core/host1x/ffmpeg/mediacodec_bridge_android.cpp
  5. 109
      src/video_core/renderer_vulkan/vk_present_manager.cpp
  6. 10
      src/video_core/renderer_vulkan/vk_present_manager.h
  7. 116
      src/video_core/vulkan_common/ahardwarebuffer_vulkan.cpp
  8. 23
      src/video_core/vulkan_common/ahardwarebuffer_vulkan.h
  9. 17
      src/video_core/vulkan_common/vulkan_device.cpp
  10. 6
      src/video_core/vulkan_common/vulkan_device.h
  11. 3
      src/video_core/vulkan_common/vulkan_wrapper.cpp
  12. 3
      src/video_core/vulkan_common/vulkan_wrapper.h

55
src/android/app/src/main/java/org/yuzu/yuzu_emu/media/NativeMediaCodec.java

@ -4,6 +4,9 @@
package org.yuzu.yuzu_emu.media;
import android.media.Image;
import android.media.ImageReader;
import android.view.Surface;
import android.hardware.HardwareBuffer;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
@ -31,8 +34,32 @@ public class NativeMediaCodec {
}
final int id = nextId.getAndIncrement();
decoders.put(id, codec);
// Request YUV_420_888 output (Image) if available
// Use ImageReader-backed Surface so the codec can produce AHardwareBuffer-backed images
ImageReader imageReader = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
imageReader = ImageReader.newInstance(width, height, android.graphics.ImageFormat.YUV_420_888, 4);
final ImageReader ir = imageReader;
ir.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
Image image = null;
try {
image = reader.acquireLatestImage();
if (image == null) return;
long pts = image.getTimestamp();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
HardwareBuffer hb = image.getHardwareBuffer();
if (hb != null) {
onHardwareBufferAvailable(id, hb, pts);
}
}
} catch (Throwable t) {
Log.w(TAG, "onImageAvailable failed: " + t);
} finally {
if (image != null) image.close();
}
}
}, null);
codec.setCallback(new MediaCodec.Callback() {
private final int decoderId = id;
@ -43,17 +70,14 @@ public class NativeMediaCodec {
@Override
public void onOutputBufferAvailable(MediaCodec mc, int index, MediaCodec.BufferInfo info) {
// We configure the codec to output to the ImageReader surface; the ImageReader listener
// will handle delivering AHardwareBuffer-backed images to native code. Still release the
// output buffer if requested by codec (some codecs may not use getOutputImage when a
// Surface is provided).
try {
Image image = mc.getOutputImage(index);
if (image != null) {
byte[] data = ImageToNV12(image);
onFrameDecoded(decoderId, data, image.getWidth(), image.getHeight(), info.presentationTimeUs);
image.close();
}
} catch (Throwable t) {
Log.w(TAG, "onOutputBufferAvailable failed: " + t);
} finally {
try { mc.releaseOutputBuffer(index, false); } catch (Throwable ignored) {}
} catch (Throwable t) {
Log.w(TAG, "onOutputBufferAvailable release failed: " + t);
}
}
@ -69,7 +93,13 @@ public class NativeMediaCodec {
});
}
codec.configure(format, null, null, 0);
// Configure codec to output to the ImageReader surface when possible to enable GPU-backed buffers.
if (imageReader != null) {
Surface surf = imageReader.getSurface();
codec.configure(format, surf, null, 0);
} else {
codec.configure(format, null, null, 0);
}
codec.start();
return id;
} catch (Exception e) {
@ -78,6 +108,9 @@ public class NativeMediaCodec {
}
}
// Called from Java when an AHardwareBuffer-backed image is available (API26+).
private static native void onHardwareBufferAvailable(int decoderId, Object hardwareBuffer, long pts);
private static byte[] ImageToNV12(Image image) {
// Convert YUV_420_888 to NV12 (Y plane, interleaved UV)
final Image.Plane[] planes = image.getPlanes();

1
src/video_core/CMakeLists.txt

@ -333,6 +333,7 @@ target_link_options(video_core PRIVATE ${FFmpeg_LDFLAGS})
if(ANDROID)
target_sources(video_core PRIVATE host1x/ffmpeg/mediacodec_bridge_android.cpp)
target_sources(video_core PRIVATE vulkan_common/ahardwarebuffer_vulkan.cpp)
endif()
add_dependencies(video_core host_shaders)

18
src/video_core/host1x/ffmpeg/mediacodec_bridge.h

@ -7,6 +7,10 @@
#include <vector>
#include <cstdint>
#ifdef __ANDROID__
#include <android/hardware_buffer.h>
#endif
namespace FFmpeg::MediaCodecBridge {
bool IsAvailable();
@ -22,4 +26,18 @@ bool SendPacket(int id, const uint8_t* data, size_t size, int64_t pts);
// Pop a decoded NV12 frame. Returns std::nullopt if none available. On success, fills width,height,pts
std::optional<std::vector<uint8_t>> PopDecodedFrame(int id, int& width, int& height, int64_t& pts);
#ifdef __ANDROID__
// Pop a decoded AHardwareBuffer (zero-copy path). Returns std::nullopt if none available.
std::optional<AHardwareBuffer*> PopDecodedHardwareBuffer(int id, int& width, int& height, int64_t& pts);
#endif
#ifdef __ANDROID__
// Enqueue an AHardwareBuffer for presentation; PresentManager will consume these.
void EnqueueHardwareBufferForPresent(AHardwareBuffer* ahb, int width, int height, int64_t pts);
// Try to pop an AHardwareBuffer for presentation. Returns true if a buffer was popped.
bool TryPopHardwareBufferForPresent(AHardwareBuffer** out_ahb, int& out_width, int& out_height, int64_t& out_pts);
#endif
} // namespace FFmpeg::MediaCodecBridge
} // namespace FFmpeg::MediaCodecBridge

83
src/video_core/host1x/ffmpeg/mediacodec_bridge_android.cpp

@ -15,6 +15,9 @@
#include <cstdint>
#include "common/android/id_cache.h"
#include "common/logging/log.h"
#include <android/hardware_buffer_jni.h>
#include <deque>
#include <queue>
namespace FFmpeg::MediaCodecBridge {
@ -31,11 +34,45 @@ struct DecoderState {
int height = 0;
int64_t pts = 0;
bool has_frame = false;
// Queue of AHardwareBuffer pointers for zero-copy path.
std::deque<AHardwareBuffer*> hw_queue;
std::deque<int64_t> hw_pts_queue;
};
static std::mutex s_global_mtx;
static std::map<int, std::shared_ptr<DecoderState>> s_decoders;
#ifdef __ANDROID__
// Global queue for AHardwareBuffer presentation (producer: mediacodec, consumer: PresentManager)
static std::mutex s_present_queue_mtx;
static std::deque<AHardwareBuffer*> s_present_queue_ahb;
static std::deque<int> s_present_queue_w;
static std::deque<int> s_present_queue_h;
static std::deque<int64_t> s_present_queue_pts;
void EnqueueHardwareBufferForPresent(AHardwareBuffer* ahb, int width, int height, int64_t pts) {
std::lock_guard lock(s_present_queue_mtx);
s_present_queue_ahb.push_back(ahb);
s_present_queue_w.push_back(width);
s_present_queue_h.push_back(height);
s_present_queue_pts.push_back(pts);
}
bool TryPopHardwareBufferForPresent(AHardwareBuffer** out_ahb, int& out_width, int& out_height, int64_t& out_pts) {
std::lock_guard lock(s_present_queue_mtx);
if (s_present_queue_ahb.empty()) return false;
*out_ahb = s_present_queue_ahb.front();
out_width = s_present_queue_w.front();
out_height = s_present_queue_h.front();
out_pts = s_present_queue_pts.front();
s_present_queue_ahb.pop_front();
s_present_queue_w.pop_front();
s_present_queue_h.pop_front();
s_present_queue_pts.pop_front();
return true;
}
#endif
extern "C" JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_media_NativeMediaCodec_onFrameDecoded(
JNIEnv* env, jclass, jint decoderId, jbyteArray data, jint width, jint height, jlong pts) {
std::lock_guard lock(s_global_mtx);
@ -51,6 +88,32 @@ extern "C" JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_media_NativeMediaCodec
st->has_frame = true;
}
extern "C" JNIEXPORT void JNICALL Java_org_yuzu_yuzu_1emu_media_NativeMediaCodec_onHardwareBufferAvailable(
JNIEnv* env, jclass, jint decoderId, jobject hardwareBuffer, jlong pts) {
if (hardwareBuffer == nullptr) return;
std::lock_guard lock(s_global_mtx);
auto it = s_decoders.find(decoderId);
if (it == s_decoders.end()) return;
auto& st = it->second;
// Convert Java HardwareBuffer to AHardwareBuffer*
AHardwareBuffer* ahb = AHardwareBuffer_fromHardwareBuffer(env, hardwareBuffer);
if (!ahb) return;
// Acquire to extend lifetime
AHardwareBuffer_acquire(ahb);
// Optionally get description to set width/height for caller convenience
AHardwareBuffer_Desc desc;
AHardwareBuffer_describe(ahb, &desc);
st->hw_queue.push_back(ahb);
st->hw_pts_queue.push_back(static_cast<int64_t>(pts));
LOG_DEBUG(Render_Vulkan, "Enqueued AHardwareBuffer for decoder {} size={}x{} pts={}", decoderId, desc.width, desc.height, (long long)pts);
st->has_frame = true;
// Also enqueue for presentation (global present queue). PresentManager will consume these.
EnqueueHardwareBufferForPresent(ahb, static_cast<int>(desc.width), static_cast<int>(desc.height), static_cast<int64_t>(pts));
}
bool IsAvailable() {
// We assume the bridge is available if the Java class can be found.
auto env = Common::Android::GetEnvForThread();
@ -116,4 +179,24 @@ std::optional<std::vector<uint8_t>> PopDecodedFrame(int id, int& width, int& hei
} // namespace FFmpeg::MediaCodecBridge
#ifdef __ANDROID__
std::optional<AHardwareBuffer*> FFmpeg::MediaCodecBridge::PopDecodedHardwareBuffer(int id, int& width, int& height, int64_t& pts) {
std::lock_guard lock(s_global_mtx);
auto it = s_decoders.find(id);
if (it == s_decoders.end()) return std::nullopt;
auto& st = it->second;
if (st->hw_queue.empty()) return std::nullopt;
AHardwareBuffer* ahb = st->hw_queue.front();
st->hw_queue.pop_front();
pts = st->hw_pts_queue.front();
st->hw_pts_queue.pop_front();
// Fill width/height from the buffer description
AHardwareBuffer_Desc desc;
AHardwareBuffer_describe(ahb, &desc);
width = static_cast<int>(desc.width);
height = static_cast<int>(desc.height);
return std::optional<AHardwareBuffer*>{ahb};
}
#endif
#endif // __ANDROID__

109
src/video_core/renderer_vulkan/vk_present_manager.cpp

@ -5,6 +5,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/settings.h"
#include "common/logging/log.h"
#include "common/thread.h"
#include "core/frontend/emu_window.h"
#include "video_core/renderer_vulkan/vk_present_manager.h"
@ -13,6 +14,10 @@
#include "video_core/vulkan_common/vulkan_device.h"
#include "video_core/vulkan_common/vulkan_surface.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
#ifdef __ANDROID__
#include "video_core/vulkan_common/ahardwarebuffer_vulkan.h"
#include "video_core/host1x/ffmpeg/mediacodec_bridge.h"
#endif
namespace Vulkan {
@ -170,6 +175,23 @@ Frame* PresentManager::GetRenderFrame() {
frame->present_done.Wait();
frame->present_done.Reset();
#ifdef ANDROID
// Free any imported AHardwareBuffer-backed resources from the previous submission.
VkDevice vkdev = static_cast<VkDevice>(*device.GetLogical());
for (VkImage img : frame->imported_images) {
vkDestroyImage(vkdev, img, nullptr);
}
for (VkDeviceMemory mem : frame->imported_mem) {
vkFreeMemory(vkdev, mem, nullptr);
}
for (AHardwareBuffer* ahb : frame->imported_ahb) {
AHardwareBuffer_release(ahb);
}
frame->imported_images.clear();
frame->imported_mem.clear();
frame->imported_ahb.clear();
#endif
return frame;
}
@ -366,6 +388,12 @@ void PresentManager::CopyToSwapchainImpl(Frame* frame) {
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
.pInheritanceInfo = nullptr,
});
// If Android MediaCodec produced an AHardwareBuffer-backed frame, import and blit it directly to
// the swapchain image. This implements a prototype zero-copy path: the mediacodec bridge enqueues
// AHardwareBuffer handles which are then imported here and copied into the swapchain image.
// We'll attempt to consume an AHardwareBuffer-backed frame after the swapchain image
// has been transitioned to TRANSFER_DST_OPTIMAL (below). This ensures correct ordering
// and avoids doing copies before layout transitions.
const VkImage image{swapchain.CurrentImage()};
const VkExtent2D extent = swapchain.GetExtent();
@ -449,15 +477,78 @@ void PresentManager::CopyToSwapchainImpl(Frame* frame) {
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, {},
{}, {}, pre_barriers);
if (blit_supported) {
cmdbuf.BlitImage(*frame->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
MakeImageBlit(frame->width, frame->height, extent.width, extent.height),
VK_FILTER_LINEAR);
} else {
cmdbuf.CopyImage(*frame->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
MakeImageCopy(frame->width, frame->height, extent.width, extent.height));
#ifdef __ANDROID__
// Try to pop a hardware buffer produced by MediaCodec. If available and extension enabled,
// import it and copy it into the swapchain image. Imported resources are tracked on the frame
// for cleanup after presentation.
AHardwareBuffer* ahb = nullptr;
int ahb_w = 0, ahb_h = 0;
int64_t ahb_pts = 0;
bool used_imported = false;
const bool ahb_ext_supported = device.IsAndroidHardwareBufferExternalMemorySupported();
if (!ahb_ext_supported) {
// Drain and release any queued buffers to avoid leaks if producer is still enqueuing.
int drained = 0;
while (FFmpeg::MediaCodecBridge::TryPopHardwareBufferForPresent(&ahb, ahb_w, ahb_h, ahb_pts)) {
AHardwareBuffer_release(ahb);
++drained;
}
if (drained > 0) {
LOG_INFO(Render_Vulkan, "Released {} queued AHardwareBuffer(s) (extension unsupported)", drained);
}
} else if (FFmpeg::MediaCodecBridge::TryPopHardwareBufferForPresent(&ahb, ahb_w, ahb_h, ahb_pts)) {
VkDevice vkdev = static_cast<VkDevice>(*device.GetLogical());
VkPhysicalDevice vkphysical = static_cast<VkPhysicalDevice>(*device.GetPhysical());
VkImage imported_image = VK_NULL_HANDLE;
VkDeviceMemory imported_mem = VK_NULL_HANDLE;
VkFormat imported_fmt = VK_FORMAT_UNDEFINED;
if (::ImportAHardwareBufferToVk(device.GetDispatchLoader(), vkdev, vkphysical, ahb, &imported_image, &imported_mem, &imported_fmt)) {
// Track imported resources on the frame so they can be freed after present
frame->imported_images.push_back(imported_image);
frame->imported_mem.push_back(imported_mem);
frame->imported_ahb.push_back(ahb);
LOG_INFO(Render_Vulkan, "Imported AHardwareBuffer {}x{} fmt={} pts={} into Vulkan image", ahb_w, ahb_h, imported_fmt, (long long)ahb_pts);
// Transition imported image to TRANSFER_SRC_OPTIMAL
VkImageMemoryBarrier barrier_src{.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = 0,
.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = imported_image,
.subresourceRange{.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1}};
const std::array<VkImageMemoryBarrier, 1> src_barriers{barrier_src};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, {}, {}, {}, src_barriers);
// Copy imported image -> swapchain image
VkImageCopy copy_region = MakeImageCopy(static_cast<u32>(ahb_w), static_cast<u32>(ahb_h), extent.width, extent.height);
cmdbuf.CopyImage(imported_image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &copy_region);
used_imported = true;
} else {
LOG_WARNING(Render_Vulkan, "AHardwareBuffer import failed; releasing buffer ({}x{} pts={})", ahb_w, ahb_h, (long long)ahb_pts);
AHardwareBuffer_release(ahb);
}
}
#endif
if (!used_imported) {
if (blit_supported) {
cmdbuf.BlitImage(*frame->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
MakeImageBlit(frame->width, frame->height, extent.width, extent.height),
VK_FILTER_LINEAR);
} else {
cmdbuf.CopyImage(*frame->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image,
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
MakeImageCopy(frame->width, frame->height, extent.width, extent.height));
}
if (ahb_ext_supported) {
LOG_DEBUG(Render_Vulkan, "No AHardwareBuffer available; used standard frame copy");
}
}
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, {},

10
src/video_core/renderer_vulkan/vk_present_manager.h

@ -6,11 +6,15 @@
#include <condition_variable>
#include <mutex>
#include <queue>
#include <vector>
#include "common/common_types.h"
#include "common/polyfill_thread.h"
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
#ifdef ANDROID
#include <android/hardware_buffer.h>
#endif
struct VkSurfaceKHR_T;
@ -33,6 +37,12 @@ struct Frame {
vk::CommandBuffer cmdbuf;
vk::Semaphore render_ready;
vk::Fence present_done;
#ifdef ANDROID
// Imported resources for a submitted frame that must be freed after present is done.
std::vector<VkImage> imported_images;
std::vector<VkDeviceMemory> imported_mem;
std::vector<AHardwareBuffer*> imported_ahb;
#endif
};
class PresentManager {

116
src/video_core/vulkan_common/ahardwarebuffer_vulkan.cpp

@ -0,0 +1,116 @@
#ifdef __ANDROID__
#include "ahardwarebuffer_vulkan.h"
#include <vulkan/vulkan.h>
#include <vulkan/vk_android_external_memory_android_hardware_buffer.h>
#include <android/hardware_buffer.h>
#include <cstring>
namespace {
static uint32_t FindMemoryTypeIndex(VkPhysicalDevice physicalDevice, uint32_t typeBits,
VkMemoryPropertyFlags properties) {
VkPhysicalDeviceMemoryProperties memProps;
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProps);
for (uint32_t i = 0; i < memProps.memoryTypeCount; ++i) {
if ((typeBits & (1u << i)) && (memProps.memoryTypes[i].propertyFlags & properties) == properties) {
return i;
}
}
// Fallback: return first matching bit
for (uint32_t i = 0; i < memProps.memoryTypeCount; ++i) {
if (typeBits & (1u << i)) return i;
}
return 0;
}
} // anonymous
static bool ImportAHardwareBufferInternal(PFN_vkGetAndroidHardwareBufferPropertiesANDROID get_props,
VkDevice device, VkPhysicalDevice physicalDevice, AHardwareBuffer* ahb,
VkImage* outImage, VkDeviceMemory* outMemory, VkFormat* outFormat) {
if (!device || !physicalDevice || !ahb || !outImage || !outMemory || !get_props) return false;
VkAndroidHardwareBufferPropertiesANDROID ahb_props{};
ahb_props.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID;
VkResult r = get_props(device, ahb, &ahb_props);
if (r != VK_SUCCESS) return false;
// Determine VkFormat to use
VkFormat fmt = static_cast<VkFormat>(ahb_props.format);
if (outFormat) *outFormat = fmt;
// Create image with external memory
VkExternalMemoryImageCreateInfo external_info{};
external_info.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_IMAGE_CREATE_INFO;
external_info.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID;
VkImageCreateInfo image_info{};
image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
image_info.pNext = &external_info;
image_info.imageType = VK_IMAGE_TYPE_2D;
image_info.format = fmt;
image_info.extent.width = ahb_props.allocationSize ? 0 : 0; // placeholder; we'll query via AHardwareBuffer_describe
AHardwareBuffer_Desc desc;
AHardwareBuffer_describe(ahb, &desc);
image_info.extent.width = desc.width;
image_info.extent.height = desc.height;
image_info.extent.depth = 1;
image_info.mipLevels = 1;
image_info.arrayLayers = 1;
image_info.samples = VK_SAMPLE_COUNT_1_BIT;
image_info.tiling = VK_IMAGE_TILING_OPTIMAL;
// Use typical usage bits; adjust as needed by consumer
image_info.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
r = vkCreateImage(device, &image_info, nullptr, outImage);
if (r != VK_SUCCESS) return false;
VkMemoryRequirements memReq;
vkGetImageMemoryRequirements(device, *outImage, &memReq);
// Prepare import struct
VkImportAndroidHardwareBufferInfoANDROID import_info{};
import_info.sType = VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID;
import_info.buffer = ahb;
VkMemoryAllocateInfo alloc_info{};
alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
alloc_info.pNext = &import_info;
alloc_info.allocationSize = memReq.size;
alloc_info.memoryTypeIndex = FindMemoryTypeIndex(physicalDevice, memReq.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
r = vkAllocateMemory(device, &alloc_info, nullptr, outMemory);
if (r != VK_SUCCESS) {
vkDestroyImage(device, *outImage, nullptr);
return false;
}
r = vkBindImageMemory(device, *outImage, *outMemory, 0);
if (r != VK_SUCCESS) {
vkDestroyImage(device, *outImage, nullptr);
vkFreeMemory(device, *outMemory, nullptr);
return false;
}
return true;
}
bool ImportAHardwareBufferToVk(VkDevice device, VkPhysicalDevice physicalDevice, AHardwareBuffer* ahb,
VkImage* outImage, VkDeviceMemory* outMemory, VkFormat* outFormat) {
PFN_vkGetAndroidHardwareBufferPropertiesANDROID fpGetAHBProps =
(PFN_vkGetAndroidHardwareBufferPropertiesANDROID)vkGetDeviceProcAddr(device, "vkGetAndroidHardwareBufferPropertiesANDROID");
return ImportAHardwareBufferInternal(fpGetAHBProps, device, physicalDevice, ahb, outImage, outMemory, outFormat);
}
bool ImportAHardwareBufferToVk(const Vulkan::vk::DeviceDispatch& dld,
VkDevice device, VkPhysicalDevice physicalDevice, AHardwareBuffer* ahb,
VkImage* outImage, VkDeviceMemory* outMemory, VkFormat* outFormat) {
return ImportAHardwareBufferInternal(dld.vkGetAndroidHardwareBufferPropertiesANDROID, device, physicalDevice, ahb,
outImage, outMemory, outFormat);
}
#endif // __ANDROID__

23
src/video_core/vulkan_common/ahardwarebuffer_vulkan.h

@ -0,0 +1,23 @@
#pragma once
#ifdef __ANDROID__
#include <vulkan/vulkan.h>
#include <android/hardware_buffer.h>
#include "video_core/vulkan_common/vulkan_wrapper.h"
// Import an AHardwareBuffer into Vulkan as a VkImage with bound memory.
// On success returns true and fills outImage/outMemory/outFormat. Caller is responsible
// for destroying the VkImage and freeing the VkDeviceMemory when done. The AHardwareBuffer
// reference is not released by this function; caller should call AHardwareBuffer_release when
// done with the AHardwareBuffer.
bool ImportAHardwareBufferToVk(VkDevice device, VkPhysicalDevice physicalDevice, AHardwareBuffer* ahb,
VkImage* outImage, VkDeviceMemory* outMemory, VkFormat* outFormat);
// Overload that uses the dispatch loader for extension calls.
bool ImportAHardwareBufferToVk(const Vulkan::vk::DeviceDispatch& dld,
VkDevice device, VkPhysicalDevice physicalDevice, AHardwareBuffer* ahb,
VkImage* outImage, VkDeviceMemory* outMemory, VkFormat* outFormat);
#endif // __ANDROID__

17
src/video_core/vulkan_common/vulkan_device.cpp

@ -1110,6 +1110,23 @@ bool Device::GetSuitability(bool requires_swapchain) {
CHECK_EXTENSION(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
}
#ifdef ANDROID
// Prefer enabling VK_ANDROID_external_memory_android_hardware_buffer when available so
// MediaCodec AHardwareBuffer frames can be imported with zero-copy.
if (supported_extensions.contains(VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME)) {
if (!loaded_extensions.contains(VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME)) {
loaded_extensions.insert(VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME);
extensions.external_memory_android_hardware_buffer = true;
LOG_INFO(Render_Vulkan, "Enabled VK_ANDROID_external_memory_android_hardware_buffer for AHardwareBuffer import path");
}
else {
LOG_INFO(Render_Vulkan, "VK_ANDROID_external_memory_android_hardware_buffer already enabled");
}
} else {
LOG_INFO(Render_Vulkan, "Device does not advertise VK_ANDROID_external_memory_android_hardware_buffer; falling back to CPU decode copy path");
}
#endif
#undef LOG_EXTENSION
#undef CHECK_EXTENSION

6
src/video_core/vulkan_common/vulkan_device.h

@ -91,6 +91,7 @@ VK_DEFINE_HANDLE(VmaAllocator)
EXTENSION(EXT, DESCRIPTOR_INDEXING, descriptor_indexing) \
EXTENSION(EXT, FILTER_CUBIC, filter_cubic) \
EXTENSION(QCOM, FILTER_CUBIC_WEIGHTS, filter_cubic_weights) \
EXTENSION(ANDROID, EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER, external_memory_android_hardware_buffer) \
EXTENSION(KHR, MAINTENANCE_1, maintenance1) \
EXTENSION(KHR, MAINTENANCE_2, maintenance2) \
EXTENSION(KHR, MAINTENANCE_3, maintenance3) \
@ -514,6 +515,11 @@ public:
return extensions.image_format_list || instance_version >= VK_API_VERSION_1_2;
}
/// Returns true if VK_ANDROID_external_memory_android_hardware_buffer is enabled.
bool IsAndroidHardwareBufferExternalMemorySupported() const {
return extensions.external_memory_android_hardware_buffer;
}
/// Returns true if the device supports VK_EXT_primitive_topology_list_restart.
bool IsTopologyListPrimitiveRestartSupported() const {
return features.primitive_topology_list_restart.primitiveTopologyListRestart;

3
src/video_core/vulkan_common/vulkan_wrapper.cpp

@ -223,6 +223,9 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
X(vkGetPipelineExecutablePropertiesKHR);
X(vkGetPipelineExecutableStatisticsKHR);
X(vkGetSemaphoreCounterValue);
// Android AHardwareBuffer external memory extension (present on Android when enabled)
X(vkGetAndroidHardwareBufferPropertiesANDROID);
X(vkGetMemoryAndroidHardwareBufferANDROID);
X(vkMapMemory);
X(vkQueueSubmit);
X(vkResetFences);

3
src/video_core/vulkan_common/vulkan_wrapper.h

@ -323,6 +323,9 @@ struct DeviceDispatch : InstanceDispatch {
PFN_vkGetPipelineExecutableStatisticsKHR vkGetPipelineExecutableStatisticsKHR{};
PFN_vkGetQueryPoolResults vkGetQueryPoolResults{};
PFN_vkGetSemaphoreCounterValue vkGetSemaphoreCounterValue{};
// Android hardware buffer external memory extension functions
PFN_vkGetAndroidHardwareBufferPropertiesANDROID vkGetAndroidHardwareBufferPropertiesANDROID{};
PFN_vkGetMemoryAndroidHardwareBufferANDROID vkGetMemoryAndroidHardwareBufferANDROID{};
PFN_vkMapMemory vkMapMemory{};
PFN_vkQueueSubmit vkQueueSubmit{};
PFN_vkResetFences vkResetFences{};

Loading…
Cancel
Save