implement phone actor with UE5 RHI texture import for Vulkan HardwareBuffer

This commit is contained in:
2026-01-17 14:53:19 +01:00
parent 87191abbce
commit b349b86108
10 changed files with 545 additions and 77 deletions

View File

@@ -12,24 +12,33 @@ The MosisSDK plugin connects to the MosisService Android application via AIDL (A
## Architecture ## Architecture
``` ```
┌─────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────────────────────────────────────────────
│ Unreal Engine Game │ │ Unreal Engine Game │
┌─────────────────────┐ ┌─────────────────────────────┐
│ UMosisPhoneComponent│◄───│ MosisVulkanTexture │ ┌─────────────────┐ ┌──────────────────────────────────────────┐
│ │ (Touch Input) (HardwareBuffer Import) │ │ │ │ AMosisPhoneActor│ UMosisPhoneTexture
└──────────┬──────────┘ └──────────────▲──────────────┘ │ (Plane Mesh) │ │ │ │
(Material) │
▼ │ └────────┬────────┘ ┌────────────────────────────────────┐ │
┌─────────────────────────────────────────┴───────────────┐ │ │ │ FMosisPhoneTextureResource │ │
│ ▼ │ │ │ │ │ │
│ ┌─────────────────────┐ │ │ ▼ ENQUEUE_RENDER_CMD │ │ │
│ │ UMosisPhoneComponent│──┼──► IVulkanDynamicRHI:: │ │ │
│ │ (Touch Input) │ │ │ RHICreateTexture2DFromAndroid │ │ │
│ │ (Callbacks) │ │ │ HardwareBuffer() │ │ │
│ └──────────┬──────────┘ │ └────────────────────────────────────┘ │ │
│ │ └──────────────────────────────────────────┘ │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ MosisClient │ │ │ │ MosisClient │ │
│ │ (AIDL IMosisListener) │ │ │ │ (AIDL IMosisListener) │ │
│ └────────────────────────────────────────────────────────┘│ │ └──────────────────────────────┬───────────────────────────────────┘
└────────────────────────────────────────────────────────────┘ └─────────────────────────────────┼───────────────────────────────────────┘
│ Binder IPC │ Binder IPC
┌────────────────────────────────────────────────────────────┐ ┌─────────────────────────────────▼───────────────────────────────────────┐
│ MosisService │ │ MosisService │
│ (Renders phone UI via RmlUi) │ │ (Renders phone UI via RmlUi) │
└─────────────────────────────────────────────────────────────┘ └─────────────────────────────────────────────────────────────────────────
``` ```
## Prerequisites ## Prerequisites
@@ -73,12 +82,14 @@ Plugins/MosisSDK/
│ │ └── BpMosisService.h │ │ └── BpMosisService.h
│ ├── Public/ │ ├── Public/
│ │ ├── MosisSDK.h # Module interface │ │ ├── MosisSDK.h # Module interface
│ │ ├── MosisPhoneComponent.h # UE5 component │ │ ├── MosisPhoneComponent.h # UE5 component (touch, callbacks)
│ │ └── MosisPhoneActor.h # Blueprint actor │ │ └── MosisPhoneActor.h # Blueprint actor (mesh, material)
│ └── Private/ │ └── Private/
│ ├── MosisSDK.cpp # Module + JNI callbacks │ ├── MosisSDK.cpp # Module + JNI callbacks
│ ├── MosisClient.h/cpp # AIDL client implementation │ ├── MosisClient.h/cpp # AIDL client implementation
│ ├── MosisVulkanTexture.h/cpp # Vulkan HardwareBuffer import │ ├── MosisPhoneTexture.h/cpp # UTexture for phone screen
│ ├── MosisPhoneTextureResource.h/cpp # Render thread resource
│ ├── MosisVulkanTexture.h/cpp # Legacy Vulkan import (unused)
│ ├── MosisPhoneComponent.cpp │ ├── MosisPhoneComponent.cpp
│ ├── MosisPhoneActor.cpp │ ├── MosisPhoneActor.cpp
│ └── Android/ │ └── Android/
@@ -152,7 +163,8 @@ The separation ensures Windows builds don't try to compile Android-specific AIDL
| Slate, SlateCore | ✓ | ✓ | UI framework | | Slate, SlateCore | ✓ | ✓ | UI framework |
| RenderCore, RHI | ✓ | ✓ | Rendering abstraction | | RenderCore, RHI | ✓ | ✓ | Rendering abstraction |
| Launch, ApplicationCore | ✗ | ✓ | Android JNI access | | Launch, ApplicationCore | ✗ | ✓ | Android JNI access |
| Vulkan | ✗ | ✓ | Hardware buffer import | | VulkanRHI | ✗ | ✓ | IVulkanDynamicRHI for HardwareBuffer import |
| Vulkan | ✗ | ✓ | Vulkan headers |
| binder_ndk, android, nativewindow | ✗ | ✓ | Android system libs | | binder_ndk, android, nativewindow | ✗ | ✓ | Android system libs |
### NDK Header Compatibility ### NDK Header Compatibility
@@ -353,6 +365,13 @@ HandTrackingVersion=V2
## Version History ## Version History
- **v1.1** - UE5 RHI texture integration
- New `UMosisPhoneTexture` using UE5's `IVulkanDynamicRHI::RHICreateTexture2DFromAndroidHardwareBuffer()`
- `FMosisPhoneTextureResource` for render thread HardwareBuffer import
- `AMosisPhoneActor` now includes default plane mesh and material setup
- Thread-safe callbacks using `TWeakObjectPtr` and `TAtomic`
- Automatic texture-to-material binding in tick
- **v1.0** - Initial implementation - **v1.0** - Initial implementation
- AIDL client for MosisService connection - AIDL client for MosisService connection
- Vulkan HardwareBuffer import - Vulkan HardwareBuffer import

View File

@@ -35,7 +35,8 @@ public class MosisSDK : ModuleRules
// Android-specific module dependencies // Android-specific module dependencies
PrivateDependencyModuleNames.AddRange(new string[] { PrivateDependencyModuleNames.AddRange(new string[] {
"Launch", "Launch",
"ApplicationCore" "ApplicationCore",
"VulkanRHI"
}); });
// Add Vulkan support (Vulkan headers for HardwareBuffer import) // Add Vulkan support (Vulkan headers for HardwareBuffer import)

View File

@@ -4,17 +4,27 @@
#include "Components/StaticMeshComponent.h" #include "Components/StaticMeshComponent.h"
#include "Materials/MaterialInstanceDynamic.h" #include "Materials/MaterialInstanceDynamic.h"
#include "Engine/StaticMesh.h" #include "Engine/StaticMesh.h"
#include "UObject/ConstructorHelpers.h"
DEFINE_LOG_CATEGORY_STATIC(LogMosisPhoneActor, Log, All); DEFINE_LOG_CATEGORY_STATIC(LogMosisPhoneActor, Log, All);
AMosisPhoneActor::AMosisPhoneActor() AMosisPhoneActor::AMosisPhoneActor()
{ {
PrimaryActorTick.bCanEverTick = false; PrimaryActorTick.bCanEverTick = true;
PrimaryActorTick.bStartWithTickEnabled = true;
// Create phone mesh component // Create phone mesh component
PhoneMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("PhoneMesh")); PhoneMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("PhoneMesh"));
RootComponent = PhoneMesh; RootComponent = PhoneMesh;
// Load the default plane mesh
static ConstructorHelpers::FObjectFinder<UStaticMesh> PlaneMeshFinder(
TEXT("/Engine/BasicShapes/Plane.Plane"));
if (PlaneMeshFinder.Succeeded())
{
PhoneMesh->SetStaticMesh(PlaneMeshFinder.Object);
}
// Create phone component // Create phone component
PhoneComponent = CreateDefaultSubobject<UMosisPhoneComponent>(TEXT("PhoneComponent")); PhoneComponent = CreateDefaultSubobject<UMosisPhoneComponent>(TEXT("PhoneComponent"));
} }
@@ -25,13 +35,34 @@ void AMosisPhoneActor::BeginPlay()
UE_LOG(LogMosisPhoneActor, Log, TEXT("BeginPlay")); UE_LOG(LogMosisPhoneActor, Log, TEXT("BeginPlay"));
// Scale mesh to match phone screen size
// The default plane is 100x100 units, so scale accordingly
if (PhoneMesh)
{
float ScaleX = ScreenSizeWorld.X / 100.0f;
float ScaleY = ScreenSizeWorld.Y / 100.0f;
PhoneMesh->SetRelativeScale3D(FVector(ScaleX, ScaleY, 1.0f));
// Update screen bounds based on scaled size
float HalfWidth = ScreenSizeWorld.X / 2.0f;
float HalfHeight = ScreenSizeWorld.Y / 2.0f;
ScreenBoundsLocal = FVector4(-HalfWidth, -HalfHeight, HalfWidth, HalfHeight);
}
// Create dynamic material for the screen // Create dynamic material for the screen
if (PhoneMesh) if (PhoneMesh)
{ {
UMaterialInterface* BaseMaterial = PhoneMesh->GetMaterial(0); UMaterialInterface* MaterialToUse = BaseMaterial;
if (BaseMaterial)
// If no base material set, use the mesh's existing material
if (!MaterialToUse)
{ {
ScreenMaterial = UMaterialInstanceDynamic::Create(BaseMaterial, this); MaterialToUse = PhoneMesh->GetMaterial(0);
}
if (MaterialToUse)
{
ScreenMaterial = UMaterialInstanceDynamic::Create(MaterialToUse, this);
PhoneMesh->SetMaterial(0, ScreenMaterial); PhoneMesh->SetMaterial(0, ScreenMaterial);
// Set it on the phone component // Set it on the phone component
@@ -42,6 +73,25 @@ void AMosisPhoneActor::BeginPlay()
UE_LOG(LogMosisPhoneActor, Log, TEXT("BeginPlay: Created dynamic material")); UE_LOG(LogMosisPhoneActor, Log, TEXT("BeginPlay: Created dynamic material"));
} }
else
{
UE_LOG(LogMosisPhoneActor, Warning, TEXT("BeginPlay: No base material available"));
}
}
}
void AMosisPhoneActor::Tick(float DeltaSeconds)
{
Super::Tick(DeltaSeconds);
// Update material texture if phone component has a new texture
if (ScreenMaterial && PhoneComponent)
{
UTexture* PhoneTexture = PhoneComponent->GetPhoneTexture();
if (PhoneTexture)
{
ScreenMaterial->SetTextureParameterValue(PhoneComponent->TextureParameterName, PhoneTexture);
}
} }
} }

View File

@@ -2,13 +2,12 @@
#include "MosisPhoneComponent.h" #include "MosisPhoneComponent.h"
#include "MosisSDK.h" #include "MosisSDK.h"
#include "Engine/Texture2D.h" #include "MosisPhoneTexture.h"
#include "Materials/MaterialInstanceDynamic.h" #include "Materials/MaterialInstanceDynamic.h"
#include "RenderingThread.h" #include "RenderingThread.h"
#if PLATFORM_ANDROID #if PLATFORM_ANDROID
#include "MosisClient.h" #include "MosisClient.h"
#include "MosisVulkanTexture.h"
#endif #endif
DEFINE_LOG_CATEGORY_STATIC(LogMosisPhone, Log, All); DEFINE_LOG_CATEGORY_STATIC(LogMosisPhone, Log, All);
@@ -30,15 +29,22 @@ void UMosisPhoneComponent::BeginPlay()
auto Client = FMosisSDKModule::GetClient(); auto Client = FMosisSDKModule::GetClient();
if (Client) if (Client)
{ {
// Set up callbacks // Use weak pointer to prevent dangling references in callbacks
Client->SetBufferCallback([this](AHardwareBuffer* buffer) { TWeakObjectPtr<UMosisPhoneComponent> WeakThis(this);
// This runs on the binder thread - flag for main thread processing
bNeedsTextureCreate = true; // Set up callbacks (these run on binder thread)
Client->SetBufferCallback([WeakThis](AHardwareBuffer* buffer) {
if (WeakThis.IsValid())
{
WeakThis->bNeedsTextureCreate.Store(true);
}
}); });
Client->SetFrameCallback([this]() { Client->SetFrameCallback([WeakThis]() {
// This runs on the binder thread - flag for tick processing if (WeakThis.IsValid())
bPendingTextureUpdate = true; {
WeakThis->bPendingTextureUpdate.Store(true);
}
}); });
UE_LOG(LogMosisPhone, Log, TEXT("BeginPlay: Callbacks registered")); UE_LOG(LogMosisPhone, Log, TEXT("BeginPlay: Callbacks registered"));
@@ -55,12 +61,6 @@ void UMosisPhoneComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
UE_LOG(LogMosisPhone, Log, TEXT("EndPlay")); UE_LOG(LogMosisPhone, Log, TEXT("EndPlay"));
#if PLATFORM_ANDROID #if PLATFORM_ANDROID
// Clean up Vulkan texture
if (VulkanTexture)
{
VulkanTexture.Reset();
}
// Clear callbacks // Clear callbacks
auto Client = FMosisSDKModule::GetClient(); auto Client = FMosisSDKModule::GetClient();
if (Client) if (Client)
@@ -70,6 +70,9 @@ void UMosisPhoneComponent::EndPlay(const EEndPlayReason::Type EndPlayReason)
} }
#endif #endif
// Release texture
PhoneTexture = nullptr;
Super::EndPlay(EndPlayReason); Super::EndPlay(EndPlayReason);
} }
@@ -79,16 +82,14 @@ void UMosisPhoneComponent::TickComponent(float DeltaTime, ELevelTick TickType, F
#if PLATFORM_ANDROID #if PLATFORM_ANDROID
// Check if we need to create texture from new buffer // Check if we need to create texture from new buffer
if (bNeedsTextureCreate) if (bNeedsTextureCreate.Exchange(false))
{ {
bNeedsTextureCreate = false;
OnBufferAvailable(); OnBufferAvailable();
} }
// Check if we need to update texture for new frame // Check if we need to update texture for new frame
if (bPendingTextureUpdate && VulkanTexture && VulkanTexture->IsValid()) if (bPendingTextureUpdate.Exchange(false) && PhoneTexture)
{ {
bPendingTextureUpdate = false;
OnFrameAvailable(); OnFrameAvailable();
} }
#endif #endif
@@ -155,25 +156,17 @@ void UMosisPhoneComponent::OnBufferAvailable()
UE_LOG(LogMosisPhone, Log, TEXT("OnBufferAvailable: %dx%d"), ScreenWidth, ScreenHeight); UE_LOG(LogMosisPhone, Log, TEXT("OnBufferAvailable: %dx%d"), ScreenWidth, ScreenHeight);
// TODO: Initialize Vulkan texture on render thread // Create phone texture if needed
// This requires accessing UE5's Vulkan RHI which has platform-specific APIs.
// For now, we log the buffer availability and dimensions.
// Full implementation would:
// 1. Get VkDevice from GVulkanRHI or IVulkanDynamicRHI
// 2. Create MosisVulkanTexture and import the HardwareBuffer
// 3. Create UTexture2D wrapping the Vulkan image
// Create placeholder texture
if (!PhoneTexture) if (!PhoneTexture)
{ {
PhoneTexture = UTexture2D::CreateTransient(ScreenWidth, ScreenHeight, PF_R8G8B8A8); PhoneTexture = NewObject<UMosisPhoneTexture>(this);
if (PhoneTexture) PhoneTexture->Initialize(ScreenWidth, ScreenHeight);
{ UE_LOG(LogMosisPhone, Log, TEXT("OnBufferAvailable: Created phone texture"));
PhoneTexture->UpdateResource();
UE_LOG(LogMosisPhone, Log, TEXT("OnBufferAvailable: Created placeholder texture"));
}
} }
// Import the hardware buffer
PhoneTexture->UpdateFromHardwareBuffer(Buffer);
// Update material if set // Update material if set
if (PhoneMaterial && PhoneTexture) if (PhoneMaterial && PhoneTexture)
{ {
@@ -185,19 +178,23 @@ void UMosisPhoneComponent::OnBufferAvailable()
void UMosisPhoneComponent::OnFrameAvailable() void UMosisPhoneComponent::OnFrameAvailable()
{ {
#if PLATFORM_ANDROID #if PLATFORM_ANDROID
if (!VulkanTexture || !VulkanTexture->IsValid()) if (!PhoneTexture)
{ {
return; return;
} }
// TODO: Copy texture on render thread // Notify texture that a new frame is available
// This requires accessing UE5's Vulkan command buffer management. // This schedules the render thread copy
// Full implementation would enqueue a render command to copy the PhoneTexture->NotifyFrameAvailable();
// imported image to the local image.
#endif #endif
} }
void UMosisPhoneComponent::UpdateTextureOnRenderThread() void UMosisPhoneComponent::UpdateTextureOnRenderThread()
{ {
// Reserved for render thread texture updates // No longer needed - handled by UMosisPhoneTexture
}
UTexture* UMosisPhoneComponent::GetPhoneTexture() const
{
return PhoneTexture;
} }

View File

@@ -0,0 +1,94 @@
// Copyright OmixLab LTD. All Rights Reserved.
#include "MosisPhoneTexture.h"
#include "MosisPhoneTextureResource.h"
#include "RenderingThread.h"
DEFINE_LOG_CATEGORY_STATIC(LogMosisPhoneTexture, Log, All);
UMosisPhoneTexture::UMosisPhoneTexture()
{
// Default initialization
SRGB = true;
NeverStream = true;
LODGroup = TEXTUREGROUP_UI;
}
void UMosisPhoneTexture::Initialize(uint32 InWidth, uint32 InHeight)
{
Width = InWidth;
Height = InHeight;
UE_LOG(LogMosisPhoneTexture, Log, TEXT("Initialize: %dx%d"), Width, Height);
// Create the resource
UpdateResource();
}
FTextureResource* UMosisPhoneTexture::CreateResource()
{
UE_LOG(LogMosisPhoneTexture, Log, TEXT("CreateResource: %dx%d"), Width, Height);
PhoneTextureResource = new FMosisPhoneTextureResource(Width, Height);
return PhoneTextureResource;
}
bool UMosisPhoneTexture::IsReady() const
{
return PhoneTextureResource && PhoneTextureResource->IsReady();
}
#if PLATFORM_ANDROID
void UMosisPhoneTexture::UpdateFromHardwareBuffer(AHardwareBuffer* Buffer)
{
if (!Buffer)
{
UE_LOG(LogMosisPhoneTexture, Warning, TEXT("UpdateFromHardwareBuffer: null buffer"));
return;
}
if (!PhoneTextureResource)
{
UE_LOG(LogMosisPhoneTexture, Warning, TEXT("UpdateFromHardwareBuffer: resource not initialized"));
return;
}
// Store buffer for render thread
PendingBuffer.Store(Buffer);
// Capture resource pointer for lambda
FMosisPhoneTextureResource* Resource = PhoneTextureResource;
AHardwareBuffer* BufferToImport = Buffer;
// Schedule render thread work
ENQUEUE_RENDER_COMMAND(MosisImportHardwareBuffer)(
[Resource, BufferToImport](FRHICommandListImmediate& RHICmdList)
{
Resource->UpdateFromHardwareBuffer_RenderThread(BufferToImport);
}
);
UE_LOG(LogMosisPhoneTexture, Log, TEXT("UpdateFromHardwareBuffer: Scheduled import"));
}
void UMosisPhoneTexture::NotifyFrameAvailable()
{
if (!PhoneTextureResource)
{
return;
}
// Capture resource pointer for lambda
FMosisPhoneTextureResource* Resource = PhoneTextureResource;
// Schedule render thread copy
ENQUEUE_RENDER_COMMAND(MosisCopyFrame)(
[Resource](FRHICommandListImmediate& RHICmdList)
{
Resource->CopySourceToLocal_RenderThread(RHICmdList);
}
);
}
#endif // PLATFORM_ANDROID

View File

@@ -0,0 +1,84 @@
// Copyright OmixLab LTD. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "Engine/Texture.h"
#include "MosisPhoneTexture.generated.h"
#if PLATFORM_ANDROID
#include <android/hardware_buffer.h>
#endif
class FMosisPhoneTextureResource;
/**
* UMosisPhoneTexture - Custom texture that displays the Mosis phone screen.
*
* This texture is updated from an AHardwareBuffer received from MosisService.
* Uses UE5's IVulkanDynamicRHI for efficient hardware buffer import.
*
* Usage:
* 1. Create via NewObject<UMosisPhoneTexture>() with width/height
* 2. Call UpdateResource() to initialize
* 3. Call UpdateFromHardwareBuffer() when buffer is received
* 4. Call NotifyFrameAvailable() each frame when new content is ready
*/
UCLASS()
class UMosisPhoneTexture : public UTexture
{
GENERATED_BODY()
public:
UMosisPhoneTexture();
/** Initialize the texture with dimensions */
void Initialize(uint32 InWidth, uint32 InHeight);
#if PLATFORM_ANDROID
/**
* Update the texture from a hardware buffer.
* Schedules render thread work to import the buffer.
* @param Buffer The AHardwareBuffer from MosisService
*/
void UpdateFromHardwareBuffer(AHardwareBuffer* Buffer);
/**
* Notify that a new frame is available.
* Schedules render thread work to copy the source to local texture.
*/
void NotifyFrameAvailable();
#endif
/** Check if the texture is ready for rendering */
bool IsReady() const;
// UTexture interface
virtual FTextureResource* CreateResource() override;
virtual EMaterialValueType GetMaterialType() const override { return MCT_Texture2D; }
virtual float GetSurfaceWidth() const override { return Width; }
virtual float GetSurfaceHeight() const override { return Height; }
virtual float GetSurfaceDepth() const override { return 0; }
virtual uint32 GetSurfaceArraySize() const override { return 0; }
protected:
/** Width of the phone screen */
UPROPERTY()
uint32 Width = 540;
/** Height of the phone screen */
UPROPERTY()
uint32 Height = 1170;
private:
/** Our custom texture resource */
FMosisPhoneTextureResource* PhoneTextureResource = nullptr;
#if PLATFORM_ANDROID
/** Pending hardware buffer to import */
TAtomic<AHardwareBuffer*> PendingBuffer{nullptr};
/** Flag indicating new frame is available */
TAtomic<bool> bFrameAvailable{false};
#endif
};

View File

@@ -0,0 +1,133 @@
// Copyright OmixLab LTD. All Rights Reserved.
#include "MosisPhoneTextureResource.h"
#include "RenderingThread.h"
#include "RHICommandList.h"
#if PLATFORM_ANDROID
#include "IVulkanDynamicRHI.h"
#endif
DEFINE_LOG_CATEGORY_STATIC(LogMosisTextureResource, Log, All);
FMosisPhoneTextureResource::FMosisPhoneTextureResource(uint32 InWidth, uint32 InHeight)
: Width(InWidth)
, Height(InHeight)
{
}
FMosisPhoneTextureResource::~FMosisPhoneTextureResource()
{
}
void FMosisPhoneTextureResource::InitRHI(FRHICommandListBase& RHICmdList)
{
UE_LOG(LogMosisTextureResource, Log, TEXT("InitRHI: %dx%d"), Width, Height);
// Create local texture for rendering
const FRHITextureCreateDesc Desc =
FRHITextureCreateDesc::Create2D(TEXT("MosisPhoneLocalTexture"), Width, Height, PF_R8G8B8A8)
.SetFlags(ETextureCreateFlags::ShaderResource | ETextureCreateFlags::RenderTargetable)
.SetInitialState(ERHIAccess::SRVMask);
LocalTextureRHI = RHICreateTexture(Desc);
if (LocalTextureRHI.IsValid())
{
UE_LOG(LogMosisTextureResource, Log, TEXT("InitRHI: Local texture created"));
// Create default SRV
TextureRHI = LocalTextureRHI;
}
else
{
UE_LOG(LogMosisTextureResource, Error, TEXT("InitRHI: Failed to create local texture"));
}
}
void FMosisPhoneTextureResource::ReleaseRHI()
{
UE_LOG(LogMosisTextureResource, Log, TEXT("ReleaseRHI"));
#if PLATFORM_ANDROID
SourceTextureRHI.SafeRelease();
CurrentBuffer = nullptr;
#endif
LocalTextureRHI.SafeRelease();
TextureRHI.SafeRelease();
bIsReady = false;
}
#if PLATFORM_ANDROID
void FMosisPhoneTextureResource::UpdateFromHardwareBuffer_RenderThread(AHardwareBuffer* Buffer)
{
check(IsInRenderingThread());
if (!Buffer)
{
UE_LOG(LogMosisTextureResource, Warning, TEXT("UpdateFromHardwareBuffer: null buffer"));
return;
}
// Check if this is a new buffer
if (Buffer == CurrentBuffer && SourceTextureRHI.IsValid())
{
return;
}
// Get buffer dimensions
AHardwareBuffer_Desc Desc{};
AHardwareBuffer_describe(Buffer, &Desc);
UE_LOG(LogMosisTextureResource, Log, TEXT("UpdateFromHardwareBuffer: Importing %dx%d buffer"),
Desc.width, Desc.height);
// Release old source texture
SourceTextureRHI.SafeRelease();
// Get the Vulkan RHI interface
IVulkanDynamicRHI* VulkanRHI = GetIVulkanDynamicRHI();
if (!VulkanRHI)
{
UE_LOG(LogMosisTextureResource, Error, TEXT("UpdateFromHardwareBuffer: VulkanRHI not available"));
return;
}
// Import the hardware buffer using UE5's built-in API
// This handles all Vulkan extension setup internally
SourceTextureRHI = VulkanRHI->RHICreateTexture2DFromAndroidHardwareBuffer(Buffer);
if (SourceTextureRHI.IsValid())
{
CurrentBuffer = Buffer;
UE_LOG(LogMosisTextureResource, Log, TEXT("UpdateFromHardwareBuffer: Import successful"));
}
else
{
UE_LOG(LogMosisTextureResource, Error, TEXT("UpdateFromHardwareBuffer: Import failed"));
}
}
void FMosisPhoneTextureResource::CopySourceToLocal_RenderThread(FRHICommandListImmediate& RHICmdList)
{
check(IsInRenderingThread());
if (!SourceTextureRHI.IsValid() || !LocalTextureRHI.IsValid())
{
return;
}
// Copy source to local texture
FRHICopyTextureInfo CopyInfo;
CopyInfo.Size.X = Width;
CopyInfo.Size.Y = Height;
CopyInfo.Size.Z = 1;
RHICmdList.CopyTexture(SourceTextureRHI, LocalTextureRHI, CopyInfo);
bIsReady = true;
}
#endif // PLATFORM_ANDROID

View File

@@ -0,0 +1,76 @@
// Copyright OmixLab LTD. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "TextureResource.h"
#include "RHI.h"
#include "RHIResources.h"
#if PLATFORM_ANDROID
#include <android/hardware_buffer.h>
#endif
/**
* FMosisPhoneTextureResource - Render resource for importing AHardwareBuffer via UE5's Vulkan RHI.
*
* Uses IVulkanDynamicRHI::RHICreateTexture2DFromAndroidHardwareBuffer() for import,
* which handles all Vulkan extension setup internally.
*
* Thread Safety:
* - UpdateFromHardwareBuffer_RenderThread: Call from render thread only
* - CopySourceToLocal_RenderThread: Call from render thread only
* - Buffer pointer exchange uses FCriticalSection
*/
class FMosisPhoneTextureResource : public FTextureResource
{
public:
FMosisPhoneTextureResource(uint32 InWidth, uint32 InHeight);
virtual ~FMosisPhoneTextureResource();
// FTextureResource interface
virtual void InitRHI(FRHICommandListBase& RHICmdList) override;
virtual void ReleaseRHI() override;
virtual uint32 GetSizeX() const override { return Width; }
virtual uint32 GetSizeY() const override { return Height; }
#if PLATFORM_ANDROID
/**
* Import a hardware buffer on the render thread.
* Creates source texture from the buffer.
* @param Buffer The AHardwareBuffer from MosisService
*/
void UpdateFromHardwareBuffer_RenderThread(AHardwareBuffer* Buffer);
/**
* Copy from source (imported) texture to local texture.
* Call this each frame when a new frame is available.
* @param RHICmdList The RHI command list for copy operations
*/
void CopySourceToLocal_RenderThread(FRHICommandListImmediate& RHICmdList);
#endif
/** Check if we have a valid local texture for rendering */
bool IsReady() const { return bIsReady; }
/** Get the local texture for material sampling */
FTextureRHIRef GetLocalTexture() const { return LocalTextureRHI; }
private:
uint32 Width;
uint32 Height;
/** Local texture copy safe for rendering (not shared with MosisService) */
FTextureRHIRef LocalTextureRHI;
#if PLATFORM_ANDROID
/** Source texture imported from AHardwareBuffer */
FTextureRHIRef SourceTextureRHI;
/** Currently imported hardware buffer */
AHardwareBuffer* CurrentBuffer = nullptr;
#endif
/** True when local texture is ready for rendering */
TAtomic<bool> bIsReady{false};
};

View File

@@ -13,6 +13,9 @@ class UMaterialInstanceDynamic;
/** /**
* AMosisPhoneActor - Pre-configured actor for displaying the Mosis phone. * AMosisPhoneActor - Pre-configured actor for displaying the Mosis phone.
* Contains mesh, material, and phone component ready for use. * Contains mesh, material, and phone component ready for use.
*
* By default, uses Engine's basic plane mesh. For custom phone models,
* set the PhoneMesh property to your mesh asset.
*/ */
UCLASS(BlueprintType, Blueprintable) UCLASS(BlueprintType, Blueprintable)
class MOSISSDK_API AMosisPhoneActor : public AActor class MOSISSDK_API AMosisPhoneActor : public AActor
@@ -23,6 +26,7 @@ public:
AMosisPhoneActor(); AMosisPhoneActor();
virtual void BeginPlay() override; virtual void BeginPlay() override;
virtual void Tick(float DeltaSeconds) override;
/** /**
* Send a touch event to the phone at world coordinates. * Send a touch event to the phone at world coordinates.
@@ -66,6 +70,21 @@ protected:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Mosis") UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Mosis")
FVector4 ScreenBoundsLocal = FVector4(-50.0f, -100.0f, 50.0f, 100.0f); FVector4 ScreenBoundsLocal = FVector4(-50.0f, -100.0f, 50.0f, 100.0f);
/**
* Base material to use for the phone screen.
* Should have a texture parameter matching TextureParameterName on the PhoneComponent.
* If not set, a simple unlit emissive material will be created.
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Mosis")
TObjectPtr<UMaterialInterface> BaseMaterial;
/**
* Phone screen dimensions in world units.
* Default is 10x21.7 cm (typical smartphone aspect ratio 9:19.5)
*/
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Mosis")
FVector2D ScreenSizeWorld = FVector2D(10.0f, 21.7f);
private: private:
/** Convert world hit to UV coordinates */ /** Convert world hit to UV coordinates */
bool WorldToPhoneUV(FVector WorldLocation, FVector2D& OutUV) const; bool WorldToPhoneUV(FVector WorldLocation, FVector2D& OutUV) const;

View File

@@ -7,7 +7,7 @@
#include "MosisPhoneComponent.generated.h" #include "MosisPhoneComponent.generated.h"
class UMaterialInstanceDynamic; class UMaterialInstanceDynamic;
class UTexture2D; class UMosisPhoneTexture;
/** /**
* Touch event type for phone interactions. * Touch event type for phone interactions.
@@ -50,7 +50,7 @@ public:
* @return The texture showing the phone screen, or nullptr if not ready * @return The texture showing the phone screen, or nullptr if not ready
*/ */
UFUNCTION(BlueprintCallable, Category = "Mosis") UFUNCTION(BlueprintCallable, Category = "Mosis")
UTexture2D* GetPhoneTexture() const { return PhoneTexture; } UTexture* GetPhoneTexture() const;
/** /**
* Check if the phone is connected and ready. * Check if the phone is connected and ready.
@@ -90,20 +90,15 @@ protected:
private: private:
/** Phone screen texture */ /** Phone screen texture */
UPROPERTY() UPROPERTY()
UTexture2D* PhoneTexture; TObjectPtr<UMosisPhoneTexture> PhoneTexture;
/** Screen dimensions from service */ /** Screen dimensions from service */
int32 ScreenWidth = 0; int32 ScreenWidth = 0;
int32 ScreenHeight = 0; int32 ScreenHeight = 0;
/** Flag to track pending texture updates */ /** Flag to track pending texture updates */
bool bPendingTextureUpdate = false; TAtomic<bool> bPendingTextureUpdate{false};
/** Flag to track if we need to recreate the texture */ /** Flag to track if we need to recreate the texture */
bool bNeedsTextureCreate = false; TAtomic<bool> bNeedsTextureCreate{false};
#if PLATFORM_ANDROID
/** Vulkan texture wrapper */
TSharedPtr<class MosisVulkanTexture> VulkanTexture;
#endif
}; };