浏览代码

Added utility to copy channels from textures with compute buffer (generate C# and compute shader)

/stochastic_alpha_test
Frédéric Vauchelles 7 年前
当前提交
275d1f0a
共有 18 个文件被更改,包括 332 次插入93 次删除
  1. 4
      ScriptableRenderPipeline/HDRenderPipeline/HDRenderPipeline.cs
  2. 17
      ScriptableRenderPipeline/HDRenderPipeline/HDUtils.cs
  3. 2
      ScriptableRenderPipeline/HDRenderPipeline/RenderPipelineResources/HDRenderPipelineResources.asset
  4. 2
      ScriptableRenderPipeline/HDRenderPipeline/RenderPipelineResources/HDRenderPipelineResources.asset.meta
  5. 15
      ScriptableRenderPipeline/HDRenderPipeline/RenderPipelineResources/RenderPipelineResources.cs
  6. 23
      ScriptableRenderPipeline/Core/Resources/GPUCopy.compute
  7. 10
      ScriptableRenderPipeline/Core/Resources/GPUCopy.compute.meta
  8. 33
      ScriptableRenderPipeline/Core/Resources/GPUCopy.cs
  9. 13
      ScriptableRenderPipeline/Core/Resources/GPUCopy.cs.meta
  10. 146
      ScriptableRenderPipeline/Core/Resources/GPUCopyAsset.cs
  11. 13
      ScriptableRenderPipeline/Core/Resources/GPUCopyAsset.cs.meta
  12. 17
      ScriptableRenderPipeline/Core/Resources/GPUCopyDefinition.asset
  13. 10
      ScriptableRenderPipeline/Core/Resources/GPUCopyDefinition.asset.meta
  14. 39
      ScriptableRenderPipeline/Core/Resources/Editor/GPUCopyAssetEditor.cs
  15. 13
      ScriptableRenderPipeline/Core/Resources/Editor/GPUCopyAssetEditor.cs.meta
  16. 58
      ScriptableRenderPipeline/HDRenderPipeline/RenderPipelineResources/CopyChannel.compute
  17. 10
      ScriptableRenderPipeline/HDRenderPipeline/RenderPipelineResources/CopyChannel.compute.meta

4
ScriptableRenderPipeline/HDRenderPipeline/HDRenderPipeline.cs


Material m_CopyStencilForSplitLighting;
Material m_CopyStencilForRegularLighting;
GPUCopy m_GPUCopy;
// Various set of material use in render loop
ComputeShader m_SubsurfaceScatteringCS { get { return m_Asset.renderPipelineResources.subsurfaceScatteringCS; } }

public HDRenderPipeline(HDRenderPipelineAsset asset)
{
m_Asset = asset;
m_GPUCopy = new GPUCopy(asset.renderPipelineResources.copyChannelCS);
// Scan material list and assign it
m_MaterialList = CoreUtils.GetRenderPipelineMaterialList();

cmd.SetGlobalVector(HDShaderIDs._DepthPyramidMipSize, new Vector4(size, size, lodCount, 0));
cmd.GetTemporaryRT(HDShaderIDs._DepthPyramidMips[0], size, size, 0, FilterMode.Bilinear, RenderTextureFormat.RFloat, RenderTextureReadWrite.Linear, 1, true);
HDUtils.SampleCopyChannel_xyzw2x(cmd, GetDepthTexture(), HDShaderIDs._DepthPyramidMips[0], new Vector2(size, size), m_Asset.renderPipelineResources);
m_GPUCopy.SampleCopyChannel_xyzw2x(cmd, GetDepthTexture(), HDShaderIDs._DepthPyramidMips[0], new Vector2(size, size));
cmd.CopyTexture(HDShaderIDs._DepthPyramidMips[0], 0, 0, m_DepthPyramidBuffer, 0, 0);
for (int i = 0; i < lodCount; i++)

17
ScriptableRenderPipeline/HDRenderPipeline/HDUtils.cs


var relativePath = fullPath.Substring(fullPath.IndexOf("Assets"));
return relativePath.Replace("\\", "/") + "/";
}
public static string GetCorePath()
{
var hdrpPath = GetHDRenderPipelinePath();
var fullPath = Path.GetFullPath(hdrpPath + "../Core");
var relativePath = fullPath.Substring(fullPath.IndexOf("Assets"));
return relativePath.Replace("\\", "/") + "/";
}
#endif
public const RendererConfiguration k_RendererConfigurationBakedLighting = RendererConfiguration.PerObjectLightProbe | RendererConfiguration.PerObjectLightmaps | RendererConfiguration.PerObjectLightProbeProxyVolume;

y -= s_OverlayLineHeight;
s_OverlayLineHeight = -1.0f;
}
}
public static void SampleCopyChannel_xyzw2x(CommandBuffer cmd, RenderTargetIdentifier source, RenderTargetIdentifier target, Vector2 size, RenderPipelineResources resources)
{
var s = new Vector4(size.x, size.y, 1f / size.x, 1f / size.y);
cmd.SetComputeVectorParam(resources.copyChannelCS, HDShaderIDs._Size, s);
cmd.SetComputeTextureParam(resources.copyChannelCS, resources.copyChannelKernel_xyzw2x, HDShaderIDs._Source4, source);
cmd.SetComputeTextureParam(resources.copyChannelCS, resources.copyChannelKernel_xyzw2x, HDShaderIDs._Result1, target);
cmd.DispatchCompute(resources.copyChannelCS, resources.copyChannelKernel_xyzw2x, (int)(size.x) / 8, (int)(size.y) / 8, 1);
}
}
}

2
ScriptableRenderPipeline/HDRenderPipeline/RenderPipelineResources/HDRenderPipelineResources.asset


type: 3}
gaussianPyramidCS: {fileID: 7200000, guid: 6dba4103d23a7904fbc49099355aff3e, type: 3}
depthPyramidCS: {fileID: 7200000, guid: 64a553bb564274041906f78ffba955e4, type: 3}
copyChannelCS: {fileID: 7200000, guid: 4e910cec38a1ec640a03324015e036d0, type: 3}
copyChannelCS: {fileID: 7200000, guid: a68d8aaeb0956234d94e389f196381ee, type: 3}
clearDispatchIndirectShader: {fileID: 7200000, guid: fc1f553acb80a6446a32d33e403d0656,
type: 3}
buildDispatchIndirectShader: {fileID: 7200000, guid: 4eb1b418be7044c40bb5200496c50f14,

2
ScriptableRenderPipeline/HDRenderPipeline/RenderPipelineResources/HDRenderPipelineResources.asset.meta


fileFormatVersion: 2
guid: 42086e81f4f0c724f96f7f09cc995354
timeCreated: 1507102386
timeCreated: 1507124092
licenseType: Pro
NativeFormatImporter:
externalObjects: {}

15
ScriptableRenderPipeline/HDRenderPipeline/RenderPipelineResources/RenderPipelineResources.cs


string HDRenderPipelinePath = HDUtils.GetHDRenderPipelinePath();
string PostProcessingPath = HDUtils.GetPostProcessingPath();
string CorePath = HDUtils.GetCorePath();
instance.debugDisplayLatlongShader = UnityEditor.AssetDatabase.LoadAssetAtPath<Shader>(HDRenderPipelinePath + "Debug/DebugDisplayLatlong.Shader");
instance.debugViewMaterialGBufferShader = UnityEditor.AssetDatabase.LoadAssetAtPath<Shader>(HDRenderPipelinePath + "Debug/DebugViewMaterialGBuffer.Shader");

instance.volumetricLightingCS = UnityEditor.AssetDatabase.LoadAssetAtPath<ComputeShader>(HDRenderPipelinePath + "Lighting/Volumetrics/Resources/VolumetricLighting.compute");
instance.gaussianPyramidCS = UnityEditor.AssetDatabase.LoadAssetAtPath<ComputeShader>(PostProcessingPath + "Shaders/Builtins/GaussianDownsample.compute");
instance.depthPyramidCS = UnityEditor.AssetDatabase.LoadAssetAtPath<ComputeShader>(HDRenderPipelinePath + "RenderPipelineResources/DepthDownsample.compute");
instance.copyChannelCS = UnityEditor.AssetDatabase.LoadAssetAtPath<ComputeShader>(HDRenderPipelinePath + "RenderPipelineResources/CopyChannel.compute");
instance.copyChannelCS = UnityEditor.AssetDatabase.LoadAssetAtPath<ComputeShader>(CorePath + "Resources/GPUCopy.compute");
instance.clearDispatchIndirectShader = UnityEditor.AssetDatabase.LoadAssetAtPath<ComputeShader>(HDRenderPipelinePath + "Lighting/TilePass/cleardispatchindirect.compute");
instance.buildDispatchIndirectShader = UnityEditor.AssetDatabase.LoadAssetAtPath<ComputeShader>(HDRenderPipelinePath + "Lighting/TilePass/builddispatchindirect.compute");

public Shader GGXConvolve;
public Shader skyboxCubemap;
public int copyChannelKernel_xyzw2x { get; private set; }
public void OnEnable()
{
copyChannelKernel_xyzw2x = -1;
if (copyChannelCS != null)
{
copyChannelKernel_xyzw2x = copyChannelCS.FindKernel("KSampleCopy4_1_x");
}
}
}
}

23
ScriptableRenderPipeline/Core/Resources/GPUCopy.compute


// Autogenerated file. Do not edit by hand
#include "../ShaderLibrary/Common.hlsl"
SamplerState sampler_LinearClamp;
CBUFFER_START(cb)
float4 _Size;
CBUFFER_END
RWTexture2D<float1> _Result1;
Texture2D<float4> _Source4;
#pragma kernel KSampleCopy4_1_x
[numthreads(8, 8, 1)]
void KSampleCopy4_1_x(uint2 dispatchThreadId : SV_DispatchThreadID)
{
_Result1[dispatchThreadId] = _Source4.SampleLevel(sampler_LinearClamp, float2(dispatchThreadId) * _Size.zw, 0.0).x;
}

10
ScriptableRenderPipeline/Core/Resources/GPUCopy.compute.meta


fileFormatVersion: 2
guid: a68d8aaeb0956234d94e389f196381ee
timeCreated: 1507123133
licenseType: Pro
ComputeShaderImporter:
externalObjects: {}
currentAPIMask: 4
userData:
assetBundleName:
assetBundleVariant:

33
ScriptableRenderPipeline/Core/Resources/GPUCopy.cs


// Autogenerated file. Do not edit by hand
using System;
using UnityEngine.Rendering;
namespace UnityEngine.Experimental.Rendering
{
public class GPUCopy
{
ComputeShader m_Shader;
int k_SampleKernel_xyzw2x;
public GPUCopy(ComputeShader shader)
{
m_Shader = shader;
k_SampleKernel_xyzw2x = m_Shader.FindKernel("KSampleCopy4_1_x");
}
static readonly int _Result1 = Shader.PropertyToID("_Result1");
static readonly int _Source4 = Shader.PropertyToID("_Source4");
static readonly int _Size = Shader.PropertyToID("_Size");
public void SampleCopyChannel_xyzw2x(CommandBuffer cmd, RenderTargetIdentifier source, RenderTargetIdentifier target, Vector2 size)
{
if (size.x < 8 || size.y < 8)
Debug.LogWarning("Trying to copy a channel from a texture smaller than 8x* or *x8. ComputeShader cannot perform it.");
var s = new Vector4(size.x, size.y, 1f / size.x, 1f / size.y);
cmd.SetComputeVectorParam(m_Shader, _Size, s);
cmd.SetComputeTextureParam(m_Shader, k_SampleKernel_xyzw2x, _Source4, source);
cmd.SetComputeTextureParam(m_Shader, k_SampleKernel_xyzw2x, _Result1, target);
cmd.DispatchCompute(m_Shader, k_SampleKernel_xyzw2x, (int)(size.x) / 8, (int)(size.y) / 8, 1);
}
}
}

13
ScriptableRenderPipeline/Core/Resources/GPUCopy.cs.meta


fileFormatVersion: 2
guid: 584fa1be28adb3344ab9eec18571f46b
timeCreated: 1507123133
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

146
ScriptableRenderPipeline/Core/Resources/GPUCopyAsset.cs


using System;
using System.Collections.Generic;
using System.Text;
namespace UnityEngine.Experimental.Rendering
{
[CreateAssetMenu(fileName = "GPUCopy")]
public class GPUCopyAsset : ScriptableObject
{
static string[] k_ChannelIDS = { "x", "xy", "xyz", "xyzw" };
const int k_KernelSize = 8;
[Serializable]
public struct CopyOperation
{
public string subscript;
public int sourceChannel;
public int targetChannel;
}
[SerializeField]
CopyOperation[] m_CopyOperation = new CopyOperation[0];
public void Generate(out string computeShader, out string csharp)
{
var operations = m_CopyOperation;
var sources = new HashSet<int>();
var targets = new HashSet<int>();
var cc = new StringBuilder(); // Compute Shader out
var ccp = new StringBuilder(); // Compute properties
var cck = new StringBuilder(); // Compute kernel
var cs = new StringBuilder(); // CSharp out
var csm = new StringBuilder(); // CSharp methods
var csc = new StringBuilder(); // CSharp constructor
var csp = new StringBuilder(); // CSharp properties
for (var i = 0; i < operations.Length; i++)
{
var o = operations[i];
sources.Add(o.sourceChannel);
targets.Add(o.targetChannel);
}
ccp.AppendLine();
foreach (var target in targets)
{
ccp.AppendLine(string.Format("RWTexture2D<float{0}> _Result{0};", target.ToString()));
csm.AppendLine(string.Format(" static readonly int _Result{0} = Shader.PropertyToID(\"_Result{0}\");", target.ToString()));
}
ccp.AppendLine();
foreach (var source in sources)
{
ccp.AppendLine(string.Format("Texture2D<float{0}> _Source{0};", source.ToString()));
csm.AppendLine(string.Format(" static readonly int _Source{0} = Shader.PropertyToID(\"_Source{0}\");", source.ToString()));
}
ccp.AppendLine();
csc.AppendLine(" public GPUCopy(ComputeShader shader)");
csc.AppendLine(" {");
csc.AppendLine(" m_Shader = shader;");
csm.AppendLine(" static readonly int _Size = Shader.PropertyToID(\"_Size\");");
for (var i = 0; i < operations.Length; i++)
{
var o = operations[i];
// Compute kernel
var kernelName = string.Format("KSampleCopy{0}_{1}_{2}", o.sourceChannel.ToString(), o.targetChannel.ToString(), o.subscript);
cck.AppendLine(string.Format("#pragma kernel {0}", kernelName));
cck.AppendLine(string.Format(@"[numthreads({0}, {0}, 1)]",
k_KernelSize.ToString(), k_KernelSize.ToString()));
cck.AppendLine(string.Format(@"void {0}(uint2 dispatchThreadId : SV_DispatchThreadID)", kernelName));
cck.AppendLine("{");
cck.AppendLine(string.Format(" _Result{0}[dispatchThreadId] = _Source{1}.SampleLevel(sampler_LinearClamp, float2(dispatchThreadId) * _Size.zw, 0.0).{2};",
o.targetChannel.ToString(), o.sourceChannel.ToString(), o.subscript));
cck.AppendLine("}");
cck.AppendLine();
// CSharp kernel index
var channelName = k_ChannelIDS[o.sourceChannel - 1];
var kernelIndexName = string.Format("k_SampleKernel_{0}2{1}", channelName, o.subscript);
csp.AppendLine(string.Format(" int {0};", kernelIndexName));
// CSharp constructor
csc.AppendLine(string.Format(" {0} = m_Shader.FindKernel(\"{1}\");", kernelIndexName, kernelName));
// CSharp method
csm.AppendLine(string.Format(@" public void SampleCopyChannel_{0}2{1}(CommandBuffer cmd, RenderTargetIdentifier source, RenderTargetIdentifier target, Vector2 size)", channelName, o.subscript));
csm.AppendLine(" {");
csm.AppendLine(string.Format(" if (size.x < {0} || size.y < {0})", k_KernelSize.ToString()));
csm.AppendLine(" Debug.LogWarning(\"Trying to copy a channel from a texture smaller than 8x* or *x8. ComputeShader cannot perform it.\");");
csm.AppendLine(" var s = new Vector4(size.x, size.y, 1f / size.x, 1f / size.y);");
csm.AppendLine(" cmd.SetComputeVectorParam(m_Shader, _Size, s);");
csm.AppendLine(string.Format(" cmd.SetComputeTextureParam(m_Shader, {0}, _Source{1}, source);", kernelIndexName, o.sourceChannel.ToString()));
csm.AppendLine(string.Format(" cmd.SetComputeTextureParam(m_Shader, {0}, _Result{1}, target);", kernelIndexName, o.targetChannel.ToString()));
csm.AppendLine(string.Format(" cmd.DispatchCompute(m_Shader, {0}, (int)(size.x) / {1}, (int)(size.y) / {1}, 1);", kernelIndexName, k_KernelSize.ToString()));
csm.AppendLine(" }");
}
csc.AppendLine(" }");
// Compute Shader
cc.AppendLine(@"// Autogenerated file. Do not edit by hand");
cc.AppendLine();
cc.AppendLine(@"#include ""../ShaderLibrary/Common.hlsl""");
cc.AppendLine();
cc.AppendLine(@"SamplerState sampler_LinearClamp;");
cc.AppendLine();
cc.AppendLine(@"CBUFFER_START(cb)");
cc.AppendLine(@" float4 _Size;");
cc.AppendLine(@"CBUFFER_END");
cc.AppendLine(ccp.ToString()); // Properties
cc.AppendLine(cck.ToString()); // Kernels
// CSharp
cs.AppendLine(@"// Autogenerated file. Do not edit by hand");
cs.AppendLine(@"using System;");
cs.AppendLine(@"using UnityEngine.Rendering;");
cs.AppendLine();
cs.AppendLine(@"namespace UnityEngine.Experimental.Rendering");
cs.AppendLine("{");
cs.AppendLine(" public class GPUCopy");
cs.AppendLine(" {");
cs.AppendLine(" ComputeShader m_Shader;");
cs.AppendLine(csp.ToString()); // Properties
cs.AppendLine(csc.ToString()); // Constructor
cs.AppendLine(csm.ToString()); // methods
cs.AppendLine(" }");
cs.AppendLine("}");
computeShader = cc.ToString();
csharp = cs.ToString();
}
void OnValidate()
{
for (var i = 0; i < m_CopyOperation.Length; i++)
{
var o = m_CopyOperation[i];
o.sourceChannel = Mathf.Clamp(o.sourceChannel, 1, k_ChannelIDS.Length);
o.targetChannel = Mathf.Clamp(o.targetChannel, 1, k_ChannelIDS.Length);
m_CopyOperation[i] = o;
}
}
}
}

13
ScriptableRenderPipeline/Core/Resources/GPUCopyAsset.cs.meta


fileFormatVersion: 2
guid: d6592d54c1ab4694eaabc84e14cc6385
timeCreated: 1507119931
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

17
ScriptableRenderPipeline/Core/Resources/GPUCopyDefinition.asset


%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_PrefabParentObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: d6592d54c1ab4694eaabc84e14cc6385, type: 3}
m_Name: GPUCopyDefinition
m_EditorClassIdentifier:
m_CopyOperation:
- subscript: x
sourceChannel: 4
targetChannel: 1

10
ScriptableRenderPipeline/Core/Resources/GPUCopyDefinition.asset.meta


fileFormatVersion: 2
guid: 6aece293b43f5fb4fb5ace0d22b0035a
timeCreated: 1507122722
licenseType: Pro
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

39
ScriptableRenderPipeline/Core/Resources/Editor/GPUCopyAssetEditor.cs


using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;
using UnityEngine.Experimental.Rendering;
[CustomEditor(typeof(GPUCopyAsset))]
public class GPUCopyAssetEditor : Editor
{
GPUCopyAsset m_Target;
void OnEnable()
{
m_Target = (GPUCopyAsset)target;
}
public override void OnInspectorGUI()
{
if (GUILayout.Button("Generate"))
{
var assetpath = AssetDatabase.GetAssetPath(target);
var dirpath = Path.GetDirectoryName(assetpath);
var targetpathcs = dirpath + "/GPUCopy.cs";
var targetpathcc =dirpath + "/GPUCopy.compute";
string cc, cs;
m_Target.Generate(out cc, out cs);
File.WriteAllText(targetpathcc, cc);
File.WriteAllText(targetpathcs, cs);
AssetDatabase.StartAssetEditing();
AssetDatabase.ImportAsset(targetpathcc);
AssetDatabase.ImportAsset(targetpathcs);
AssetDatabase.StopAssetEditing();
}
base.OnInspectorGUI();
}
}

13
ScriptableRenderPipeline/Core/Resources/Editor/GPUCopyAssetEditor.cs.meta


fileFormatVersion: 2
guid: fc0d85f2322db134d96eec0e66cf1c34
timeCreated: 1507122737
licenseType: Pro
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

58
ScriptableRenderPipeline/HDRenderPipeline/RenderPipelineResources/CopyChannel.compute


#include "../../Core/ShaderLibrary/Common.hlsl"
#define SOURCE(n) _Source##n
#define RESULT(n) _Result##n
#define SOURCE_DECLARATION(n) Texture2D<float##n> SOURCE(n)
#define RESULT_DECLARATION(n) RWTexture2D<float##n> RESULT(n)
SOURCE_DECLARATION(1);
SOURCE_DECLARATION(2);
SOURCE_DECLARATION(3);
SOURCE_DECLARATION(4);
RESULT_DECLARATION(1);
RESULT_DECLARATION(2);
RESULT_DECLARATION(3);
RESULT_DECLARATION(4);
SamplerState sampler_LinearClamp;
CBUFFER_START(cb)
float4 _Size;
CBUFFER_END
#define KERNEL_SAMPLECOPY(sourceN, resultN, subscript) [numthreads(8, 8, 1)]\
void KSampleCopy##sourceN##_##resultN##_##subscript(uint2 dispatchThreadId : SV_DispatchThreadID)\
{\
RESULT(resultN)[dispatchThreadId] = SOURCE(sourceN).SampleLevel(sampler_LinearClamp, float2(dispatchThreadId) * _Size.zw, 0.0).subscript;\
}
// Source R
#pragma kernel KSampleCopy1_1_x
KERNEL_SAMPLECOPY(1, 1, x);
// Source RG
// Result R
#pragma kernel KSampleCopy2_1_x
KERNEL_SAMPLECOPY(2, 1, x);
#pragma kernel KSampleCopy2_1_y
KERNEL_SAMPLECOPY(2, 1, y);
// Result RG
#pragma kernel KSampleCopy2_2_xx
KERNEL_SAMPLECOPY(2, 2, xx);
#pragma kernel KSampleCopy2_2_xy
KERNEL_SAMPLECOPY(2, 2, xy);
#pragma kernel KSampleCopy2_2_yx
KERNEL_SAMPLECOPY(2, 2, yx);
#pragma kernel KSampleCopy2_2_yy
KERNEL_SAMPLECOPY(2, 2, yy);
// Source RGBA
// Result R
#pragma kernel KSampleCopy4_1_x
KERNEL_SAMPLECOPY(4, 1, x);
#pragma kernel KSampleCopy4_1_y
KERNEL_SAMPLECOPY(4, 1, y);
#pragma kernel KSampleCopy4_1_z
KERNEL_SAMPLECOPY(4, 1, z);
#pragma kernel KSampleCopy4_1_w
KERNEL_SAMPLECOPY(4, 1, w);

10
ScriptableRenderPipeline/HDRenderPipeline/RenderPipelineResources/CopyChannel.compute.meta


fileFormatVersion: 2
guid: 4e910cec38a1ec640a03324015e036d0
timeCreated: 1506516404
licenseType: Pro
ComputeShaderImporter:
externalObjects: {}
currentAPIMask: 4
userData:
assetBundleName:
assetBundleVariant:
正在加载...
取消
保存