mirror of https://github.com/AxioDL/boo.git
macOS API sync
This commit is contained in:
parent
592ffa1372
commit
65c99ad769
|
@ -4,9 +4,9 @@
|
||||||
[submodule "logvisor"]
|
[submodule "logvisor"]
|
||||||
path = logvisor
|
path = logvisor
|
||||||
url = ../logvisor.git
|
url = ../logvisor.git
|
||||||
[submodule "lib/graphicsdev/NX/mesa"]
|
[submodule "lib/graphicsdev/nx/mesa"]
|
||||||
path = lib/graphicsdev/nx/mesa
|
path = lib/graphicsdev/nx/mesa
|
||||||
url = ../mesa.git
|
url = ../mesa.git
|
||||||
[submodule "lib/graphicsdev/NX/libdrm_nouveau"]
|
[submodule "lib/graphicsdev/nx/libdrm_nouveau"]
|
||||||
path = lib/graphicsdev/nx/libdrm_nouveau
|
path = lib/graphicsdev/nx/libdrm_nouveau
|
||||||
url = https://github.com/devkitPro/libdrm_nouveau.git
|
url = https://github.com/devkitPro/libdrm_nouveau.git
|
||||||
|
|
|
@ -39,7 +39,7 @@ if(TARGET nx_compiler)
|
||||||
list(APPEND _BOO_SYS_DEFINES -DHECL_NOUVEAU_NX=1)
|
list(APPEND _BOO_SYS_DEFINES -DHECL_NOUVEAU_NX=1)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(NOT GEKKO AND NOT CAFE AND NOT WINDOWS_STORE AND NOT NX)
|
if(NOT GEKKO AND NOT CAFE AND NOT WINDOWS_STORE AND NOT NX AND NOT APPLE)
|
||||||
list(APPEND PLAT_SRCS
|
list(APPEND PLAT_SRCS
|
||||||
lib/graphicsdev/GL.cpp
|
lib/graphicsdev/GL.cpp
|
||||||
lib/graphicsdev/glew.c)
|
lib/graphicsdev/glew.c)
|
||||||
|
@ -137,7 +137,6 @@ elseif(APPLE)
|
||||||
|
|
||||||
find_library(APPKIT_LIBRARY AppKit)
|
find_library(APPKIT_LIBRARY AppKit)
|
||||||
find_library(IOKIT_LIBRARY IOKit)
|
find_library(IOKIT_LIBRARY IOKit)
|
||||||
find_library(OPENGL_LIBRARY OpenGL)
|
|
||||||
unset(BOO_HAS_METAL CACHE)
|
unset(BOO_HAS_METAL CACHE)
|
||||||
if (NOT CMAKE_OSX_DEPLOYMENT_TARGET OR CMAKE_OSX_DEPLOYMENT_TARGET VERSION_GREATER 10.11)
|
if (NOT CMAKE_OSX_DEPLOYMENT_TARGET OR CMAKE_OSX_DEPLOYMENT_TARGET VERSION_GREATER 10.11)
|
||||||
set(BOO_HAS_METAL ON CACHE BOOL "Metal is available in this OS X version" FORCE)
|
set(BOO_HAS_METAL ON CACHE BOOL "Metal is available in this OS X version" FORCE)
|
||||||
|
@ -151,10 +150,11 @@ elseif(APPLE)
|
||||||
find_library(AUDIOTOOLBOX_LIBRARY AudioToolbox)
|
find_library(AUDIOTOOLBOX_LIBRARY AudioToolbox)
|
||||||
find_library(COREAUDIO_LIBRARY CoreAudio)
|
find_library(COREAUDIO_LIBRARY CoreAudio)
|
||||||
find_library(COREMIDI_LIBRARY CoreMIDI)
|
find_library(COREMIDI_LIBRARY CoreMIDI)
|
||||||
list(APPEND _BOO_SYS_LIBS ${APPKIT_LIBRARY} ${IOKIT_LIBRARY} ${OPENGL_LIBRARY} ${METAL_LIBRARY}
|
list(APPEND _BOO_SYS_LIBS ${APPKIT_LIBRARY} ${IOKIT_LIBRARY} ${METAL_LIBRARY}
|
||||||
${QUARTZCORE_LIBRARY} ${COREVIDEO_LIBRARY} ${AUDIOTOOLBOX_LIBRARY}
|
${QUARTZCORE_LIBRARY} ${COREVIDEO_LIBRARY} ${AUDIOTOOLBOX_LIBRARY}
|
||||||
${COREAUDIO_LIBRARY} ${COREMIDI_LIBRARY})
|
${COREAUDIO_LIBRARY} ${COREMIDI_LIBRARY})
|
||||||
|
|
||||||
|
add_library(glew lib/graphicsdev/glew.c)
|
||||||
elseif(NX)
|
elseif(NX)
|
||||||
list(APPEND _BOO_SYS_DEFINES -DBOO_HAS_NX=1)
|
list(APPEND _BOO_SYS_DEFINES -DBOO_HAS_NX=1)
|
||||||
list(APPEND PLAT_SRCS
|
list(APPEND PLAT_SRCS
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
#include "boo/System.hpp"
|
#include "boo/System.hpp"
|
||||||
#include "boo/ThreadLocalPtr.hpp"
|
#include "boo/ThreadLocalPtr.hpp"
|
||||||
#include "boo/BooObject.hpp"
|
#include "boo/BooObject.hpp"
|
||||||
|
|
|
@ -37,36 +37,15 @@ public:
|
||||||
ObjToken<ITextureR> newRenderTexture(size_t width, size_t height, TextureClampMode clampMode,
|
ObjToken<ITextureR> newRenderTexture(size_t width, size_t height, TextureClampMode clampMode,
|
||||||
size_t colorBindCount, size_t depthBindCount);
|
size_t colorBindCount, size_t depthBindCount);
|
||||||
|
|
||||||
bool bindingNeedsVertexFormat() const { return false; }
|
ObjToken<IShaderStage> newShaderStage(const uint8_t* data, size_t size, PipelineStage stage);
|
||||||
ObjToken<IVertexFormat> newVertexFormat(size_t elementCount, const VertexElementDescriptor* elements,
|
|
||||||
size_t baseVert = 0, size_t baseInst = 0);
|
|
||||||
|
|
||||||
ObjToken<IShaderPipeline> newShaderPipeline(const char* vertSource, const char* fragSource,
|
ObjToken<IShaderPipeline> newShaderPipeline(ObjToken<IShaderStage> vertex, ObjToken<IShaderStage> fragment,
|
||||||
std::vector<uint8_t>* vertBlobOut,
|
ObjToken<IShaderStage> geometry, ObjToken<IShaderStage> control,
|
||||||
std::vector<uint8_t>* fragBlobOut,
|
ObjToken<IShaderStage> evaluation, const VertexFormatInfo& vtxFmt,
|
||||||
const ObjToken<IVertexFormat>& vtxFmt,
|
const AdditionalPipelineInfo& additionalInfo);
|
||||||
BlendFactor srcFac, BlendFactor dstFac, Primitive prim,
|
|
||||||
ZTest depthTest, bool depthWrite, bool colorWrite,
|
|
||||||
bool alphaWrite, CullMode culling,
|
|
||||||
bool overwriteAlpha = true,
|
|
||||||
bool depthAttachment = true);
|
|
||||||
|
|
||||||
ObjToken<IShaderPipeline> newTessellationShaderPipeline(
|
|
||||||
const char* computeSource, const char* fragSource,
|
|
||||||
const char* evaluationSource,
|
|
||||||
std::vector<uint8_t>* computeBlobOut,
|
|
||||||
std::vector<uint8_t>* fragBlobOut,
|
|
||||||
std::vector<uint8_t>* evaluationBlobOut,
|
|
||||||
const ObjToken<IVertexFormat>& vtxFmt,
|
|
||||||
BlendFactor srcFac, BlendFactor dstFac, uint32_t patchSize,
|
|
||||||
ZTest depthTest, bool depthWrite, bool colorWrite,
|
|
||||||
bool alphaWrite, CullMode culling,
|
|
||||||
bool overwriteAlpha = true,
|
|
||||||
bool depthAttachment = true);
|
|
||||||
|
|
||||||
ObjToken<IShaderDataBinding>
|
ObjToken<IShaderDataBinding>
|
||||||
newShaderDataBinding(const ObjToken<IShaderPipeline>& pipeline,
|
newShaderDataBinding(const ObjToken<IShaderPipeline>& pipeline,
|
||||||
const ObjToken<IVertexFormat>& vtxFormat,
|
|
||||||
const ObjToken<IGraphicsBuffer>& vbo,
|
const ObjToken<IGraphicsBuffer>& vbo,
|
||||||
const ObjToken<IGraphicsBuffer>& instVbo,
|
const ObjToken<IGraphicsBuffer>& instVbo,
|
||||||
const ObjToken<IGraphicsBuffer>& ibo,
|
const ObjToken<IGraphicsBuffer>& ibo,
|
||||||
|
@ -76,6 +55,8 @@ public:
|
||||||
const int* texBindIdxs, const bool* depthBind,
|
const int* texBindIdxs, const bool* depthBind,
|
||||||
size_t baseVert = 0, size_t baseInst = 0);
|
size_t baseVert = 0, size_t baseInst = 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static std::vector<uint8_t> CompileMetal(const char* source, PipelineStage stage);
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <typeindex>
|
#include <typeindex>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace boo
|
namespace boo
|
||||||
{
|
{
|
||||||
|
|
|
@ -67,19 +67,11 @@ static logvisor::Module Log("boo::Metal");
|
||||||
struct MetalCommandQueue;
|
struct MetalCommandQueue;
|
||||||
class MetalDataFactoryImpl;
|
class MetalDataFactoryImpl;
|
||||||
|
|
||||||
struct MetalShareableShader : IShareableShader<MetalDataFactoryImpl, MetalShareableShader>
|
|
||||||
{
|
|
||||||
id<MTLFunction> m_shader;
|
|
||||||
MetalShareableShader(MetalDataFactoryImpl& fac, uint64_t srcKey, uint64_t binKey, id<MTLFunction> s)
|
|
||||||
: IShareableShader(fac, srcKey, binKey), m_shader(s) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
class MetalDataFactoryImpl : public MetalDataFactory, public GraphicsDataFactoryHead
|
class MetalDataFactoryImpl : public MetalDataFactory, public GraphicsDataFactoryHead
|
||||||
{
|
{
|
||||||
friend struct MetalCommandQueue;
|
friend struct MetalCommandQueue;
|
||||||
friend class MetalDataFactory::Context;
|
friend class MetalDataFactory::Context;
|
||||||
IGraphicsContext* m_parent;
|
IGraphicsContext* m_parent;
|
||||||
std::unordered_map<uint64_t, std::unique_ptr<MetalShareableShader>> m_sharedShaders;
|
|
||||||
struct MetalContext* m_ctx;
|
struct MetalContext* m_ctx;
|
||||||
|
|
||||||
bool m_hasTessellation = false;
|
bool m_hasTessellation = false;
|
||||||
|
@ -88,7 +80,6 @@ class MetalDataFactoryImpl : public MetalDataFactory, public GraphicsDataFactory
|
||||||
ObjToken<IShaderPipeline> m_gammaShader;
|
ObjToken<IShaderPipeline> m_gammaShader;
|
||||||
ObjToken<ITextureD> m_gammaLUT;
|
ObjToken<ITextureD> m_gammaLUT;
|
||||||
ObjToken<IGraphicsBufferS> m_gammaVBO;
|
ObjToken<IGraphicsBufferS> m_gammaVBO;
|
||||||
ObjToken<IVertexFormat> m_gammaVFMT;
|
|
||||||
ObjToken<IShaderDataBinding> m_gammaBinding;
|
ObjToken<IShaderDataBinding> m_gammaBinding;
|
||||||
void SetupGammaResources()
|
void SetupGammaResources()
|
||||||
{
|
{
|
||||||
|
@ -96,14 +87,20 @@ class MetalDataFactoryImpl : public MetalDataFactory, public GraphicsDataFactory
|
||||||
|
|
||||||
commitTransaction([this](IGraphicsDataFactory::Context& ctx)
|
commitTransaction([this](IGraphicsDataFactory::Context& ctx)
|
||||||
{
|
{
|
||||||
|
auto vertexMetal = MetalDataFactory::CompileMetal(GammaVS, PipelineStage::Vertex);
|
||||||
|
auto vertexShader = ctx.newShaderStage(vertexMetal, PipelineStage::Vertex);
|
||||||
|
auto fragmentMetal = MetalDataFactory::CompileMetal(GammaFS, PipelineStage::Fragment);
|
||||||
|
auto fragmentShader = ctx.newShaderStage(fragmentMetal, PipelineStage::Fragment);
|
||||||
const VertexElementDescriptor vfmt[] = {
|
const VertexElementDescriptor vfmt[] = {
|
||||||
{nullptr, nullptr, VertexSemantic::Position4},
|
{VertexSemantic::Position4},
|
||||||
{nullptr, nullptr, VertexSemantic::UV4}
|
{VertexSemantic::UV4}
|
||||||
};
|
};
|
||||||
m_gammaVFMT = ctx.newVertexFormat(2, vfmt);
|
AdditionalPipelineInfo info =
|
||||||
m_gammaShader = static_cast<Context&>(ctx).newShaderPipeline(GammaVS, GammaFS,
|
{
|
||||||
nullptr, nullptr, m_gammaVFMT, BlendFactor::One, BlendFactor::Zero,
|
BlendFactor::One, BlendFactor::Zero,
|
||||||
Primitive::TriStrips, ZTest::None, false, true, false, CullMode::None, true, false);
|
Primitive::TriStrips, ZTest::None, false, true, false, CullMode::None
|
||||||
|
};
|
||||||
|
m_gammaShader = ctx.newShaderPipeline(vertexShader, fragmentShader, vfmt, info);
|
||||||
m_gammaLUT = ctx.newDynamicTexture(256, 256, TextureFormat::I16, TextureClampMode::ClampToEdge);
|
m_gammaLUT = ctx.newDynamicTexture(256, 256, TextureFormat::I16, TextureClampMode::ClampToEdge);
|
||||||
setDisplayGamma(1.f);
|
setDisplayGamma(1.f);
|
||||||
const struct Vert {
|
const struct Vert {
|
||||||
|
@ -117,242 +114,22 @@ class MetalDataFactoryImpl : public MetalDataFactory, public GraphicsDataFactory
|
||||||
};
|
};
|
||||||
m_gammaVBO = ctx.newStaticBuffer(BufferUse::Vertex, verts, 32, 4);
|
m_gammaVBO = ctx.newStaticBuffer(BufferUse::Vertex, verts, 32, 4);
|
||||||
ObjToken<ITexture> texs[] = {{}, m_gammaLUT.get()};
|
ObjToken<ITexture> texs[] = {{}, m_gammaLUT.get()};
|
||||||
m_gammaBinding = ctx.newShaderDataBinding(m_gammaShader, m_gammaVFMT, m_gammaVBO.get(), {}, {},
|
m_gammaBinding = ctx.newShaderDataBinding(m_gammaShader, m_gammaVBO.get(), {}, {},
|
||||||
0, nullptr, nullptr, 2, texs, nullptr, nullptr);
|
0, nullptr, nullptr, 2, texs, nullptr, nullptr);
|
||||||
return true;
|
return true;
|
||||||
} BooTrace);
|
} BooTrace);
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
std::unordered_map<uint64_t, uint64_t> m_sourceToBinary;
|
|
||||||
char m_libfile[MAXPATHLEN];
|
|
||||||
bool m_hasCompiler = false;
|
|
||||||
|
|
||||||
MetalDataFactoryImpl(IGraphicsContext* parent, MetalContext* ctx)
|
MetalDataFactoryImpl(IGraphicsContext* parent, MetalContext* ctx)
|
||||||
: m_parent(parent), m_ctx(ctx)
|
: m_parent(parent), m_ctx(ctx) {}
|
||||||
{
|
|
||||||
snprintf(m_libfile, MAXPATHLEN, "%sboo_metal_shader.metallib", getenv("TMPDIR"));
|
|
||||||
for (auto& arg : APP->getArgs())
|
|
||||||
if (arg == "--metal-compile")
|
|
||||||
{
|
|
||||||
m_hasCompiler = CheckForMetalCompiler();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
~MetalDataFactoryImpl() = default;
|
~MetalDataFactoryImpl() = default;
|
||||||
|
|
||||||
Platform platform() const { return Platform::Metal; }
|
Platform platform() const { return Platform::Metal; }
|
||||||
const char* platformName() const { return "Metal"; }
|
const char* platformName() const { return "Metal"; }
|
||||||
void commitTransaction(const std::function<bool(IGraphicsDataFactory::Context& ctx)>& __BooTraceArgs);
|
void commitTransaction(const std::function<bool(IGraphicsDataFactory::Context& ctx)>& __BooTraceArgs);
|
||||||
ObjToken<IGraphicsBufferD> newPoolBuffer(BufferUse use, size_t stride, size_t count __BooTraceArgs);
|
ObjToken<IGraphicsBufferD> newPoolBuffer(BufferUse use, size_t stride, size_t count __BooTraceArgs);
|
||||||
void _unregisterShareableShader(uint64_t srcKey, uint64_t binKey) { m_sharedShaders.erase(srcKey); }
|
|
||||||
|
|
||||||
static bool CheckForMetalCompiler()
|
|
||||||
{
|
|
||||||
pid_t pid = fork();
|
|
||||||
if (!pid)
|
|
||||||
{
|
|
||||||
execlp("xcrun", "xcrun", "-sdk", "macosx", "metal", NULL);
|
|
||||||
/* xcrun returns 72 if metal command not found;
|
|
||||||
* emulate that if xcrun not found */
|
|
||||||
exit(72);
|
|
||||||
}
|
|
||||||
|
|
||||||
int status, ret;
|
|
||||||
while ((ret = waitpid(pid, &status, 0)) < 0 && errno == EINTR) {}
|
|
||||||
if (ret < 0)
|
|
||||||
return false;
|
|
||||||
return WEXITSTATUS(status) == 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t CompileLib(std::vector<uint8_t>& blobOut, const char* source, uint64_t srcKey)
|
|
||||||
{
|
|
||||||
if (!m_hasCompiler)
|
|
||||||
{
|
|
||||||
/* Cache the source if there's no compiler */
|
|
||||||
size_t sourceLen = strlen(source);
|
|
||||||
|
|
||||||
/* First byte unset to indicate source data */
|
|
||||||
blobOut.resize(sourceLen + 2);
|
|
||||||
memcpy(&blobOut[1], source, sourceLen);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
/* Cache the binary otherwise */
|
|
||||||
int compilerOut[2];
|
|
||||||
int compilerIn[2];
|
|
||||||
pipe(compilerOut);
|
|
||||||
pipe(compilerIn);
|
|
||||||
|
|
||||||
/* Pipe source write to compiler */
|
|
||||||
pid_t compilerPid = fork();
|
|
||||||
if (!compilerPid)
|
|
||||||
{
|
|
||||||
dup2(compilerIn[0], STDIN_FILENO);
|
|
||||||
dup2(compilerOut[1], STDOUT_FILENO);
|
|
||||||
|
|
||||||
close(compilerOut[0]);
|
|
||||||
close(compilerOut[1]);
|
|
||||||
close(compilerIn[0]);
|
|
||||||
close(compilerIn[1]);
|
|
||||||
|
|
||||||
execlp("xcrun", "xcrun", "-sdk", "macosx", "metal", "-o", "/dev/stdout", "-Wno-unused-variable",
|
|
||||||
"-Wno-unused-const-variable", "-Wno-unused-function", "-x", "metal", "-", NULL);
|
|
||||||
fprintf(stderr, "execlp fail %s\n", strerror(errno));
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
close(compilerIn[0]);
|
|
||||||
close(compilerOut[1]);
|
|
||||||
|
|
||||||
/* Pipe compiler to linker */
|
|
||||||
pid_t linkerPid = fork();
|
|
||||||
if (!linkerPid)
|
|
||||||
{
|
|
||||||
dup2(compilerOut[0], STDIN_FILENO);
|
|
||||||
|
|
||||||
close(compilerOut[0]);
|
|
||||||
close(compilerIn[1]);
|
|
||||||
|
|
||||||
/* metallib doesn't like outputting to a pipe, so temp file will have to do */
|
|
||||||
execlp("xcrun", "xcrun", "-sdk", "macosx", "metallib", "-", "-o", m_libfile, NULL);
|
|
||||||
fprintf(stderr, "execlp fail %s\n", strerror(errno));
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
close(compilerOut[0]);
|
|
||||||
|
|
||||||
/* Stream in source */
|
|
||||||
const char* inPtr = source;
|
|
||||||
size_t inRem = strlen(source);
|
|
||||||
while (inRem)
|
|
||||||
{
|
|
||||||
ssize_t writeRes = write(compilerIn[1], inPtr, inRem);
|
|
||||||
if (writeRes < 0)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "write fail %s\n", strerror(errno));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
inPtr += writeRes;
|
|
||||||
inRem -= writeRes;
|
|
||||||
}
|
|
||||||
close(compilerIn[1]);
|
|
||||||
|
|
||||||
/* Wait for completion */
|
|
||||||
int compilerStat, linkerStat;
|
|
||||||
if (waitpid(compilerPid, &compilerStat, 0) < 0 || waitpid(linkerPid, &linkerStat, 0) < 0)
|
|
||||||
{
|
|
||||||
fprintf(stderr, "waitpid fail %s\n", strerror(errno));
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (WEXITSTATUS(compilerStat) || WEXITSTATUS(linkerStat))
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
/* Copy temp file into buffer with first byte set to indicate binary data */
|
|
||||||
FILE* fin = fopen(m_libfile, "rb");
|
|
||||||
fseek(fin, 0, SEEK_END);
|
|
||||||
long libLen = ftell(fin);
|
|
||||||
fseek(fin, 0, SEEK_SET);
|
|
||||||
blobOut.resize(libLen + 1);
|
|
||||||
blobOut[0] = 1;
|
|
||||||
fread(&blobOut[1], 1, libLen, fin);
|
|
||||||
fclose(fin);
|
|
||||||
}
|
|
||||||
|
|
||||||
XXH64_state_t hashState;
|
|
||||||
XXH64_reset(&hashState, 0);
|
|
||||||
XXH64_update(&hashState, blobOut.data(), blobOut.size());
|
|
||||||
uint64_t binKey = XXH64_digest(&hashState);
|
|
||||||
m_sourceToBinary[srcKey] = binKey;
|
|
||||||
return binKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t CompileLib(__strong id<MTLLibrary>& libOut, const char* source, uint64_t srcKey,
|
|
||||||
MTLCompileOptions* compOpts, NSError * _Nullable *err)
|
|
||||||
{
|
|
||||||
libOut = [m_ctx->m_dev newLibraryWithSource:@(source)
|
|
||||||
options:compOpts
|
|
||||||
error:err];
|
|
||||||
|
|
||||||
if (srcKey)
|
|
||||||
{
|
|
||||||
XXH64_state_t hashState;
|
|
||||||
XXH64_reset(&hashState, 0);
|
|
||||||
uint8_t zero = 0;
|
|
||||||
XXH64_update(&hashState, &zero, 1);
|
|
||||||
XXH64_update(&hashState, source, strlen(source) + 1);
|
|
||||||
uint64_t binKey = XXH64_digest(&hashState);
|
|
||||||
m_sourceToBinary[srcKey] = binKey;
|
|
||||||
return binKey;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
MetalShareableShader::Token PrepareShaderStage(const char* source, std::vector<uint8_t>* blobOut, NSString* funcName)
|
|
||||||
{
|
|
||||||
MTLCompileOptions* compOpts = [MTLCompileOptions new];
|
|
||||||
compOpts.languageVersion = MTLLanguageVersion1_2;
|
|
||||||
NSError* err = nullptr;
|
|
||||||
|
|
||||||
XXH64_state_t hashState;
|
|
||||||
uint64_t srcHash = 0;
|
|
||||||
uint64_t binHash = 0;
|
|
||||||
XXH64_reset(&hashState, 0);
|
|
||||||
if (source)
|
|
||||||
{
|
|
||||||
XXH64_update(&hashState, source, strlen(source));
|
|
||||||
srcHash = XXH64_digest(&hashState);
|
|
||||||
auto binSearch = m_sourceToBinary.find(srcHash);
|
|
||||||
if (binSearch != m_sourceToBinary.cend())
|
|
||||||
binHash = binSearch->second;
|
|
||||||
}
|
|
||||||
else if (blobOut && !blobOut->empty())
|
|
||||||
{
|
|
||||||
XXH64_update(&hashState, blobOut->data(), blobOut->size());
|
|
||||||
binHash = XXH64_digest(&hashState);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (blobOut && blobOut->empty())
|
|
||||||
binHash = CompileLib(*blobOut, source, srcHash);
|
|
||||||
|
|
||||||
MetalShareableShader::Token shader;
|
|
||||||
auto search = binHash ? m_sharedShaders.find(binHash) : m_sharedShaders.end();
|
|
||||||
if (search != m_sharedShaders.end())
|
|
||||||
{
|
|
||||||
return search->second->lock();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
id<MTLLibrary> shaderLib;
|
|
||||||
if (blobOut && !blobOut->empty())
|
|
||||||
{
|
|
||||||
if ((*blobOut)[0] == 1)
|
|
||||||
{
|
|
||||||
dispatch_data_t data = dispatch_data_create(blobOut->data() + 1, blobOut->size() - 1, nullptr, nullptr);
|
|
||||||
shaderLib = [m_ctx->m_dev newLibraryWithData:data error:&err];
|
|
||||||
if (!shaderLib)
|
|
||||||
Log.report(logvisor::Fatal, "error loading library: %s", [[err localizedDescription] UTF8String]);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
CompileLib(shaderLib, (char*)blobOut->data() + 1, 0, compOpts, &err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
binHash = CompileLib(shaderLib, source, srcHash, compOpts, &err);
|
|
||||||
|
|
||||||
if (!shaderLib)
|
|
||||||
{
|
|
||||||
printf("%s\n", source);
|
|
||||||
Log.report(logvisor::Fatal, "error compiling shader: %s", [[err localizedDescription] UTF8String]);
|
|
||||||
}
|
|
||||||
id<MTLFunction> func = [shaderLib newFunctionWithName:funcName];
|
|
||||||
|
|
||||||
auto it =
|
|
||||||
m_sharedShaders.emplace(std::make_pair(binHash,
|
|
||||||
std::make_unique<MetalShareableShader>(*this, srcHash, binHash, func))).first;
|
|
||||||
return it->second->lock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setDisplayGamma(float gamma)
|
void setDisplayGamma(float gamma)
|
||||||
{
|
{
|
||||||
|
@ -855,15 +632,14 @@ static const MTLVertexFormat SEMANTIC_TYPE_TABLE[] =
|
||||||
MTLVertexFormatFloat4
|
MTLVertexFormatFloat4
|
||||||
};
|
};
|
||||||
|
|
||||||
struct MetalVertexFormat : GraphicsDataNode<IVertexFormat>
|
struct MetalVertexFormat
|
||||||
{
|
{
|
||||||
size_t m_elementCount;
|
size_t m_elementCount;
|
||||||
MTLVertexDescriptor* m_vdesc;
|
MTLVertexDescriptor* m_vdesc;
|
||||||
size_t m_stride = 0;
|
size_t m_stride = 0;
|
||||||
size_t m_instStride = 0;
|
size_t m_instStride = 0;
|
||||||
MetalVertexFormat(const ObjToken<BaseGraphicsData>& parent,
|
MetalVertexFormat(size_t elementCount, const VertexElementDescriptor* elements)
|
||||||
size_t elementCount, const VertexElementDescriptor* elements)
|
: m_elementCount(elementCount)
|
||||||
: GraphicsDataNode<IVertexFormat>(parent), m_elementCount(elementCount)
|
|
||||||
{
|
{
|
||||||
for (size_t i=0 ; i<elementCount ; ++i)
|
for (size_t i=0 ; i<elementCount ; ++i)
|
||||||
{
|
{
|
||||||
|
@ -909,7 +685,7 @@ struct MetalVertexFormat : GraphicsDataNode<IVertexFormat>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MTLStageInputOutputDescriptor* makeTessellationComputeLayout()
|
MTLStageInputOutputDescriptor* makeTessellationComputeLayout() const
|
||||||
{
|
{
|
||||||
MTLStageInputOutputDescriptor* ret = [MTLStageInputOutputDescriptor stageInputOutputDescriptor];
|
MTLStageInputOutputDescriptor* ret = [MTLStageInputOutputDescriptor stageInputOutputDescriptor];
|
||||||
|
|
||||||
|
@ -930,7 +706,7 @@ struct MetalVertexFormat : GraphicsDataNode<IVertexFormat>
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
MTLVertexDescriptor* makeTessellationVertexLayout()
|
MTLVertexDescriptor* makeTessellationVertexLayout() const
|
||||||
{
|
{
|
||||||
MTLVertexDescriptor* ret = [MTLVertexDescriptor vertexDescriptor];
|
MTLVertexDescriptor* ret = [MTLVertexDescriptor vertexDescriptor];
|
||||||
|
|
||||||
|
@ -982,6 +758,56 @@ static const MTLPrimitiveType PRIMITIVE_TABLE[] =
|
||||||
|
|
||||||
#define COLOR_WRITE_MASK (MTLColorWriteMaskRed | MTLColorWriteMaskGreen | MTLColorWriteMaskBlue)
|
#define COLOR_WRITE_MASK (MTLColorWriteMaskRed | MTLColorWriteMaskGreen | MTLColorWriteMaskBlue)
|
||||||
|
|
||||||
|
class MetalShaderStage : public GraphicsDataNode<IShaderStage>
|
||||||
|
{
|
||||||
|
friend class MetalDataFactory;
|
||||||
|
id<MTLFunction> m_shader;
|
||||||
|
MetalShaderStage(const boo::ObjToken<BaseGraphicsData>& parent, MetalContext* ctx,
|
||||||
|
const uint8_t* data, size_t size, PipelineStage stage)
|
||||||
|
: GraphicsDataNode<IShaderStage>(parent)
|
||||||
|
{
|
||||||
|
NSError* err = nullptr;
|
||||||
|
|
||||||
|
id<MTLLibrary> shaderLib;
|
||||||
|
if (data[0] == 1)
|
||||||
|
{
|
||||||
|
dispatch_data_t d = dispatch_data_create(data + 1, size - 1, nullptr, nullptr);
|
||||||
|
shaderLib = [ctx->m_dev newLibraryWithData:d error:&err];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MTLCompileOptions* compOpts = [MTLCompileOptions new];
|
||||||
|
compOpts.languageVersion = MTLLanguageVersion1_2;
|
||||||
|
shaderLib = [ctx->m_dev newLibraryWithSource:@((const char*)(data + 1))
|
||||||
|
options:compOpts
|
||||||
|
error:&err];
|
||||||
|
if (!shaderLib)
|
||||||
|
printf("%s\n", data + 1);
|
||||||
|
}
|
||||||
|
if (!shaderLib)
|
||||||
|
Log.report(logvisor::Fatal, "error creating library: %s", [[err localizedDescription] UTF8String]);
|
||||||
|
|
||||||
|
NSString* funcName;
|
||||||
|
switch (stage)
|
||||||
|
{
|
||||||
|
case PipelineStage::Vertex:
|
||||||
|
default:
|
||||||
|
funcName = @"vmain"; break;
|
||||||
|
case PipelineStage::Fragment:
|
||||||
|
funcName = @"fmain"; break;
|
||||||
|
case PipelineStage::Geometry:
|
||||||
|
funcName = @"gmain"; break;
|
||||||
|
case PipelineStage::Control:
|
||||||
|
funcName = @"cmain"; break;
|
||||||
|
case PipelineStage::Evaluation:
|
||||||
|
funcName = @"emain"; break;
|
||||||
|
}
|
||||||
|
m_shader = [shaderLib newFunctionWithName:funcName];
|
||||||
|
}
|
||||||
|
public:
|
||||||
|
id<MTLFunction> shader() const { return m_shader; }
|
||||||
|
};
|
||||||
|
|
||||||
class MetalShaderPipeline : public GraphicsDataNode<IShaderPipeline>
|
class MetalShaderPipeline : public GraphicsDataNode<IShaderPipeline>
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
|
@ -990,17 +816,13 @@ protected:
|
||||||
friend struct MetalShaderDataBinding;
|
friend struct MetalShaderDataBinding;
|
||||||
MTLCullMode m_cullMode = MTLCullModeNone;
|
MTLCullMode m_cullMode = MTLCullModeNone;
|
||||||
MTLPrimitiveType m_drawPrim;
|
MTLPrimitiveType m_drawPrim;
|
||||||
MetalShareableShader::Token m_vert;
|
uint32_t m_patchSize;
|
||||||
MetalShareableShader::Token m_frag;
|
|
||||||
|
|
||||||
MetalShaderPipeline(const ObjToken<BaseGraphicsData>& parent,
|
MetalShaderPipeline(const boo::ObjToken<BaseGraphicsData>& parent)
|
||||||
MetalShareableShader::Token&& vert,
|
: GraphicsDataNode<IShaderPipeline>(parent) {}
|
||||||
MetalShareableShader::Token&& frag)
|
|
||||||
: GraphicsDataNode<IShaderPipeline>(parent),
|
|
||||||
m_vert(std::move(vert)), m_frag(std::move(frag))
|
|
||||||
{}
|
|
||||||
|
|
||||||
virtual void setupExtraStages(MetalContext* ctx, MTLRenderPipelineDescriptor* desc, MetalVertexFormat& cVtxFmt) {}
|
virtual void setupExtraStages(MetalContext* ctx, MTLRenderPipelineDescriptor* desc,
|
||||||
|
ObjToken<IShaderStage> compute, const MetalVertexFormat& cVtxFmt) {}
|
||||||
|
|
||||||
virtual void draw(MetalCommandQueue& q, size_t start, size_t count);
|
virtual void draw(MetalCommandQueue& q, size_t start, size_t count);
|
||||||
virtual void drawIndexed(MetalCommandQueue& q, size_t start, size_t count);
|
virtual void drawIndexed(MetalCommandQueue& q, size_t start, size_t count);
|
||||||
|
@ -1008,15 +830,17 @@ protected:
|
||||||
virtual void drawInstancesIndexed(MetalCommandQueue& q, size_t start, size_t count, size_t instCount);
|
virtual void drawInstancesIndexed(MetalCommandQueue& q, size_t start, size_t count, size_t instCount);
|
||||||
|
|
||||||
void setup(MetalContext* ctx,
|
void setup(MetalContext* ctx,
|
||||||
const ObjToken<IVertexFormat>& vtxFmt, NSUInteger targetSamples,
|
NSUInteger targetSamples,
|
||||||
BlendFactor srcFac, BlendFactor dstFac, Primitive prim,
|
ObjToken<IShaderStage> vertex,
|
||||||
ZTest depthTest, bool depthWrite, bool colorWrite,
|
ObjToken<IShaderStage> fragment,
|
||||||
bool alphaWrite, bool overwriteAlpha, CullMode culling,
|
ObjToken<IShaderStage> compute,
|
||||||
bool depthAttachment = true)
|
const VertexFormatInfo& vtxFmt,
|
||||||
|
const AdditionalPipelineInfo& info)
|
||||||
{
|
{
|
||||||
m_drawPrim = PRIMITIVE_TABLE[int(prim)];
|
m_drawPrim = PRIMITIVE_TABLE[int(info.prim)];
|
||||||
|
m_patchSize = info.patchSize;
|
||||||
|
|
||||||
switch (culling)
|
switch (info.culling)
|
||||||
{
|
{
|
||||||
case CullMode::None:
|
case CullMode::None:
|
||||||
default:
|
default:
|
||||||
|
@ -1031,22 +855,22 @@ protected:
|
||||||
}
|
}
|
||||||
|
|
||||||
MTLRenderPipelineDescriptor* desc = [MTLRenderPipelineDescriptor new];
|
MTLRenderPipelineDescriptor* desc = [MTLRenderPipelineDescriptor new];
|
||||||
desc.vertexFunction = m_vert.get().m_shader;
|
desc.vertexFunction = vertex.cast<MetalShaderStage>()->shader();
|
||||||
desc.fragmentFunction = m_frag.get().m_shader;
|
desc.fragmentFunction = fragment.cast<MetalShaderStage>()->shader();
|
||||||
MetalVertexFormat& cVtxFmt = *vtxFmt.cast<MetalVertexFormat>();
|
MetalVertexFormat cVtxFmt(vtxFmt.elementCount, vtxFmt.elements);
|
||||||
desc.vertexDescriptor = cVtxFmt.m_vdesc;
|
desc.vertexDescriptor = cVtxFmt.m_vdesc;
|
||||||
setupExtraStages(ctx, desc, cVtxFmt);
|
setupExtraStages(ctx, desc, compute, cVtxFmt);
|
||||||
desc.sampleCount = targetSamples;
|
desc.sampleCount = targetSamples;
|
||||||
desc.colorAttachments[0].pixelFormat = ctx->m_pixelFormat;
|
desc.colorAttachments[0].pixelFormat = ctx->m_pixelFormat;
|
||||||
desc.colorAttachments[0].writeMask = (colorWrite ? COLOR_WRITE_MASK : 0) |
|
desc.colorAttachments[0].writeMask = (info.colorWrite ? COLOR_WRITE_MASK : 0) |
|
||||||
(alphaWrite ? MTLColorWriteMaskAlpha : 0);
|
(info.alphaWrite ? MTLColorWriteMaskAlpha : 0);
|
||||||
desc.colorAttachments[0].blendingEnabled = dstFac != BlendFactor::Zero;
|
desc.colorAttachments[0].blendingEnabled = info.dstFac != BlendFactor::Zero;
|
||||||
if (srcFac == BlendFactor::Subtract || dstFac == BlendFactor::Subtract)
|
if (info.srcFac == BlendFactor::Subtract || info.dstFac == BlendFactor::Subtract)
|
||||||
{
|
{
|
||||||
desc.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorSourceAlpha;
|
desc.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorSourceAlpha;
|
||||||
desc.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOne;
|
desc.colorAttachments[0].destinationRGBBlendFactor = MTLBlendFactorOne;
|
||||||
desc.colorAttachments[0].rgbBlendOperation = MTLBlendOperationReverseSubtract;
|
desc.colorAttachments[0].rgbBlendOperation = MTLBlendOperationReverseSubtract;
|
||||||
if (overwriteAlpha)
|
if (info.overwriteAlpha)
|
||||||
{
|
{
|
||||||
desc.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorOne;
|
desc.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorOne;
|
||||||
desc.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorZero;
|
desc.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorZero;
|
||||||
|
@ -1061,22 +885,22 @@ protected:
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
desc.colorAttachments[0].sourceRGBBlendFactor = BLEND_FACTOR_TABLE[int(srcFac)];
|
desc.colorAttachments[0].sourceRGBBlendFactor = BLEND_FACTOR_TABLE[int(info.srcFac)];
|
||||||
desc.colorAttachments[0].destinationRGBBlendFactor = BLEND_FACTOR_TABLE[int(dstFac)];
|
desc.colorAttachments[0].destinationRGBBlendFactor = BLEND_FACTOR_TABLE[int(info.dstFac)];
|
||||||
desc.colorAttachments[0].rgbBlendOperation = MTLBlendOperationAdd;
|
desc.colorAttachments[0].rgbBlendOperation = MTLBlendOperationAdd;
|
||||||
if (overwriteAlpha)
|
if (info.overwriteAlpha)
|
||||||
{
|
{
|
||||||
desc.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorOne;
|
desc.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorOne;
|
||||||
desc.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorZero;
|
desc.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorZero;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
desc.colorAttachments[0].sourceAlphaBlendFactor = BLEND_FACTOR_TABLE[int(srcFac)];
|
desc.colorAttachments[0].sourceAlphaBlendFactor = BLEND_FACTOR_TABLE[int(info.srcFac)];
|
||||||
desc.colorAttachments[0].destinationAlphaBlendFactor = BLEND_FACTOR_TABLE[int(dstFac)];
|
desc.colorAttachments[0].destinationAlphaBlendFactor = BLEND_FACTOR_TABLE[int(info.dstFac)];
|
||||||
}
|
}
|
||||||
desc.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd;
|
desc.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd;
|
||||||
}
|
}
|
||||||
desc.depthAttachmentPixelFormat = depthAttachment ? MTLPixelFormatDepth32Float : MTLPixelFormatInvalid;
|
desc.depthAttachmentPixelFormat = info.depthAttachment ? MTLPixelFormatDepth32Float : MTLPixelFormatInvalid;
|
||||||
desc.inputPrimitiveTopology = MTLPrimitiveTopologyClassTriangle;
|
desc.inputPrimitiveTopology = MTLPrimitiveTopologyClassTriangle;
|
||||||
NSError* err = nullptr;
|
NSError* err = nullptr;
|
||||||
m_state = [ctx->m_dev newRenderPipelineStateWithDescriptor:desc error:&err];
|
m_state = [ctx->m_dev newRenderPipelineStateWithDescriptor:desc error:&err];
|
||||||
|
@ -1085,7 +909,7 @@ protected:
|
||||||
[[err localizedDescription] UTF8String]);
|
[[err localizedDescription] UTF8String]);
|
||||||
|
|
||||||
MTLDepthStencilDescriptor* dsDesc = [MTLDepthStencilDescriptor new];
|
MTLDepthStencilDescriptor* dsDesc = [MTLDepthStencilDescriptor new];
|
||||||
switch (depthTest)
|
switch (info.depthTest)
|
||||||
{
|
{
|
||||||
case ZTest::None:
|
case ZTest::None:
|
||||||
default:
|
default:
|
||||||
|
@ -1105,7 +929,7 @@ protected:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
dsDesc.depthWriteEnabled = depthWrite;
|
dsDesc.depthWriteEnabled = info.depthWrite;
|
||||||
m_dsState = [ctx->m_dev newDepthStencilStateWithDescriptor:dsDesc];
|
m_dsState = [ctx->m_dev newDepthStencilStateWithDescriptor:dsDesc];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1129,20 +953,12 @@ class MetalTessellationShaderPipeline : public MetalShaderPipeline
|
||||||
friend class MetalDataFactory;
|
friend class MetalDataFactory;
|
||||||
friend struct MetalCommandQueue;
|
friend struct MetalCommandQueue;
|
||||||
friend struct MetalShaderDataBinding;
|
friend struct MetalShaderDataBinding;
|
||||||
MetalShareableShader::Token m_compute;
|
|
||||||
uint32_t m_patchSize;
|
|
||||||
|
|
||||||
MetalTessellationShaderPipeline(
|
MetalTessellationShaderPipeline(const ObjToken<BaseGraphicsData>& parent)
|
||||||
const ObjToken<BaseGraphicsData>& parent,
|
: MetalShaderPipeline(parent) {}
|
||||||
MetalShareableShader::Token&& compute,
|
|
||||||
MetalShareableShader::Token&& frag,
|
|
||||||
MetalShareableShader::Token&& evaluation,
|
|
||||||
uint32_t patchSize)
|
|
||||||
: MetalShaderPipeline(parent, std::move(evaluation), std::move(frag)),
|
|
||||||
m_compute(std::move(compute)), m_patchSize(patchSize)
|
|
||||||
{}
|
|
||||||
|
|
||||||
void setupExtraStages(MetalContext* ctx, MTLRenderPipelineDescriptor* desc, MetalVertexFormat& cVtxFmt)
|
void setupExtraStages(MetalContext* ctx, MTLRenderPipelineDescriptor* desc,
|
||||||
|
ObjToken<IShaderStage> compute, const MetalVertexFormat& cVtxFmt)
|
||||||
{
|
{
|
||||||
desc.maxTessellationFactor = 16;
|
desc.maxTessellationFactor = 16;
|
||||||
desc.tessellationFactorScaleEnabled = NO;
|
desc.tessellationFactorScaleEnabled = NO;
|
||||||
|
@ -1154,7 +970,7 @@ class MetalTessellationShaderPipeline : public MetalShaderPipeline
|
||||||
desc.vertexDescriptor = cVtxFmt.makeTessellationVertexLayout();
|
desc.vertexDescriptor = cVtxFmt.makeTessellationVertexLayout();
|
||||||
|
|
||||||
MTLComputePipelineDescriptor* compDesc = [MTLComputePipelineDescriptor new];
|
MTLComputePipelineDescriptor* compDesc = [MTLComputePipelineDescriptor new];
|
||||||
compDesc.computeFunction = m_compute.get().m_shader;
|
compDesc.computeFunction = compute.cast<MetalShaderStage>()->shader();
|
||||||
compDesc.stageInputDescriptor = cVtxFmt.makeTessellationComputeLayout();
|
compDesc.stageInputDescriptor = cVtxFmt.makeTessellationComputeLayout();
|
||||||
|
|
||||||
NSError* err = nullptr;
|
NSError* err = nullptr;
|
||||||
|
@ -1980,77 +1796,39 @@ MetalDataFactory::Context::newRenderTexture(size_t width, size_t height, Texture
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ObjToken<IVertexFormat>
|
ObjToken<IShaderStage>
|
||||||
MetalDataFactory::Context::newVertexFormat(size_t elementCount, const VertexElementDescriptor* elements,
|
MetalDataFactory::Context::newShaderStage(const uint8_t* data, size_t size, PipelineStage stage)
|
||||||
size_t baseVert, size_t baseInst)
|
|
||||||
{
|
{
|
||||||
@autoreleasepool
|
@autoreleasepool
|
||||||
{
|
{
|
||||||
return {new struct MetalVertexFormat(m_data, elementCount, elements)};
|
MetalDataFactoryImpl& factory = static_cast<MetalDataFactoryImpl&>(m_parent);
|
||||||
|
return {new MetalShaderStage(m_data, factory.m_ctx, data, size, stage)};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ObjToken<IShaderPipeline>
|
ObjToken<IShaderPipeline>
|
||||||
MetalDataFactory::Context::newShaderPipeline(const char* vertSource, const char* fragSource,
|
MetalDataFactory::Context::newShaderPipeline(ObjToken<IShaderStage> vertex, ObjToken<IShaderStage> fragment,
|
||||||
std::vector<uint8_t>* vertBlobOut,
|
ObjToken<IShaderStage> geometry, ObjToken<IShaderStage> control,
|
||||||
std::vector<uint8_t>* fragBlobOut,
|
ObjToken<IShaderStage> evaluation, const VertexFormatInfo& vtxFmt,
|
||||||
const ObjToken<IVertexFormat>& vtxFmt,
|
const AdditionalPipelineInfo& additionalInfo)
|
||||||
BlendFactor srcFac, BlendFactor dstFac, Primitive prim,
|
|
||||||
ZTest depthTest, bool depthWrite, bool colorWrite,
|
|
||||||
bool alphaWrite, CullMode culling, bool overwriteAlpha,
|
|
||||||
bool depthAttachment)
|
|
||||||
{
|
{
|
||||||
@autoreleasepool
|
@autoreleasepool
|
||||||
{
|
{
|
||||||
MetalDataFactoryImpl& factory = static_cast<MetalDataFactoryImpl&>(m_parent);
|
MetalDataFactoryImpl& factory = static_cast<MetalDataFactoryImpl&>(m_parent);
|
||||||
|
|
||||||
MetalShareableShader::Token vertShader = factory.PrepareShaderStage(vertSource, vertBlobOut, @"vmain");
|
MetalShaderPipeline* ret;
|
||||||
MetalShareableShader::Token fragShader = factory.PrepareShaderStage(fragSource, fragBlobOut, @"fmain");
|
if (evaluation)
|
||||||
|
ret = new MetalTessellationShaderPipeline(m_data);
|
||||||
MetalShaderPipeline* ret = new MetalShaderPipeline(m_data, std::move(vertShader), std::move(fragShader));
|
else
|
||||||
ret->setup(factory.m_ctx, vtxFmt, depthAttachment ? factory.m_ctx->m_sampleCount : 1,
|
ret = new MetalShaderPipeline(m_data);
|
||||||
srcFac, dstFac, prim, depthTest, depthWrite,
|
ret->setup(factory.m_ctx, additionalInfo.depthAttachment ? factory.m_ctx->m_sampleCount : 1,
|
||||||
colorWrite, alphaWrite, overwriteAlpha, culling, depthAttachment);
|
vertex, fragment, evaluation, vtxFmt, additionalInfo);
|
||||||
return {ret};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ObjToken<IShaderPipeline> MetalDataFactory::Context::newTessellationShaderPipeline(
|
|
||||||
const char* computeSource, const char* fragSource,
|
|
||||||
const char* evaluationSource,
|
|
||||||
std::vector<uint8_t>* computeBlobOut,
|
|
||||||
std::vector<uint8_t>* fragBlobOut,
|
|
||||||
std::vector<uint8_t>* evaluationBlobOut,
|
|
||||||
const ObjToken<IVertexFormat>& vtxFmt,
|
|
||||||
BlendFactor srcFac, BlendFactor dstFac, uint32_t patchSize,
|
|
||||||
ZTest depthTest, bool depthWrite, bool colorWrite,
|
|
||||||
bool alphaWrite, CullMode culling,
|
|
||||||
bool overwriteAlpha,
|
|
||||||
bool depthAttachment)
|
|
||||||
{
|
|
||||||
@autoreleasepool
|
|
||||||
{
|
|
||||||
MetalDataFactoryImpl& factory = static_cast<MetalDataFactoryImpl&>(m_parent);
|
|
||||||
|
|
||||||
if (!factory.m_hasTessellation)
|
|
||||||
Log.report(logvisor::Fatal, "Device does not support tessellation");
|
|
||||||
|
|
||||||
MetalShareableShader::Token computeShader = factory.PrepareShaderStage(computeSource, computeBlobOut, @"cmain");
|
|
||||||
MetalShareableShader::Token fragShader = factory.PrepareShaderStage(fragSource, fragBlobOut, @"fmain");
|
|
||||||
MetalShareableShader::Token evaluationShader = factory.PrepareShaderStage(evaluationSource, evaluationBlobOut, @"emain");
|
|
||||||
|
|
||||||
MetalTessellationShaderPipeline* ret = new MetalTessellationShaderPipeline(m_data,
|
|
||||||
std::move(computeShader), std::move(fragShader), std::move(evaluationShader), patchSize);
|
|
||||||
ret->setup(factory.m_ctx, vtxFmt, depthAttachment ? factory.m_ctx->m_sampleCount : 1,
|
|
||||||
srcFac, dstFac, Primitive::Patches, depthTest, depthWrite,
|
|
||||||
colorWrite, alphaWrite, overwriteAlpha, culling, depthAttachment);
|
|
||||||
return {ret};
|
return {ret};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ObjToken<IShaderDataBinding>
|
ObjToken<IShaderDataBinding>
|
||||||
MetalDataFactory::Context::newShaderDataBinding(const ObjToken<IShaderPipeline>& pipeline,
|
MetalDataFactory::Context::newShaderDataBinding(const ObjToken<IShaderPipeline>& pipeline,
|
||||||
const ObjToken<IVertexFormat>& vtxFormat,
|
|
||||||
const ObjToken<IGraphicsBuffer>& vbo,
|
const ObjToken<IGraphicsBuffer>& vbo,
|
||||||
const ObjToken<IGraphicsBuffer>& instVbo,
|
const ObjToken<IGraphicsBuffer>& instVbo,
|
||||||
const ObjToken<IGraphicsBuffer>& ibo,
|
const ObjToken<IGraphicsBuffer>& ibo,
|
||||||
|
@ -2095,6 +1873,14 @@ std::unique_ptr<IGraphicsDataFactory> _NewMetalDataFactory(IGraphicsContext* par
|
||||||
return std::make_unique<MetalDataFactoryImpl>(parent, ctx);
|
return std::make_unique<MetalDataFactoryImpl>(parent, ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> MetalDataFactory::CompileMetal(const char* source, PipelineStage stage)
|
||||||
|
{
|
||||||
|
size_t strSz = strlen(source) + 1;
|
||||||
|
std::vector<uint8_t> ret(strSz + 1);
|
||||||
|
memcpy(ret.data() + 1, source, strSz);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
#include "boo/IApplication.hpp"
|
#include "boo/IApplication.hpp"
|
||||||
#include "boo/graphicsdev/Metal.hpp"
|
#include "boo/graphicsdev/Metal.hpp"
|
||||||
#include "boo/graphicsdev/GL.hpp"
|
|
||||||
#include "CocoaCommon.hpp"
|
#include "CocoaCommon.hpp"
|
||||||
#include "../Common.hpp"
|
#include "../Common.hpp"
|
||||||
|
|
||||||
|
@ -32,8 +31,7 @@ namespace boo
|
||||||
{
|
{
|
||||||
static logvisor::Module Log("boo::ApplicationCocoa");
|
static logvisor::Module Log("boo::ApplicationCocoa");
|
||||||
|
|
||||||
std::shared_ptr<IWindow> _WindowCocoaNew(SystemStringView title, NSOpenGLContext* lastGLCtx,
|
std::shared_ptr<IWindow> _WindowCocoaNew(SystemStringView title, MetalContext* metalCtx);
|
||||||
MetalContext* metalCtx, GLContext* glCtx);
|
|
||||||
|
|
||||||
class ApplicationCocoa : public IApplication
|
class ApplicationCocoa : public IApplication
|
||||||
{
|
{
|
||||||
|
@ -52,7 +50,9 @@ private:
|
||||||
std::unordered_map<uintptr_t, std::weak_ptr<IWindow>> m_windows;
|
std::unordered_map<uintptr_t, std::weak_ptr<IWindow>> m_windows;
|
||||||
|
|
||||||
MetalContext m_metalCtx;
|
MetalContext m_metalCtx;
|
||||||
|
#if 0
|
||||||
GLContext m_glCtx;
|
GLContext m_glCtx;
|
||||||
|
#endif
|
||||||
|
|
||||||
void _deletedWindow(IWindow* window)
|
void _deletedWindow(IWindow* window)
|
||||||
{
|
{
|
||||||
|
@ -78,9 +78,11 @@ public:
|
||||||
m_metalCtx.m_sampleCount = samples;
|
m_metalCtx.m_sampleCount = samples;
|
||||||
m_metalCtx.m_anisotropy = anisotropy;
|
m_metalCtx.m_anisotropy = anisotropy;
|
||||||
m_metalCtx.m_pixelFormat = deepColor ? MTLPixelFormatRGBA16Float : MTLPixelFormatBGRA8Unorm;
|
m_metalCtx.m_pixelFormat = deepColor ? MTLPixelFormatRGBA16Float : MTLPixelFormatBGRA8Unorm;
|
||||||
|
#if 0
|
||||||
m_glCtx.m_sampleCount = samples;
|
m_glCtx.m_sampleCount = samples;
|
||||||
m_glCtx.m_anisotropy = anisotropy;
|
m_glCtx.m_anisotropy = anisotropy;
|
||||||
m_glCtx.m_deepColor = deepColor;
|
m_glCtx.m_deepColor = deepColor;
|
||||||
|
#endif
|
||||||
|
|
||||||
[[NSApplication sharedApplication] setActivationPolicy:NSApplicationActivationPolicyRegular];
|
[[NSApplication sharedApplication] setActivationPolicy:NSApplicationActivationPolicyRegular];
|
||||||
|
|
||||||
|
@ -104,38 +106,13 @@ public:
|
||||||
action:nil keyEquivalent:@""] setSubmenu:appMenu];
|
action:nil keyEquivalent:@""] setSubmenu:appMenu];
|
||||||
[[NSApplication sharedApplication] setMainMenu:rootMenu];
|
[[NSApplication sharedApplication] setMainMenu:rootMenu];
|
||||||
|
|
||||||
/* Determine which graphics API to use */
|
m_metalCtx.m_dev = MTLCreateSystemDefaultDevice();
|
||||||
#if BOO_HAS_METAL
|
if (!m_metalCtx.m_dev)
|
||||||
bool useGL = false;
|
Log.report(logvisor::Fatal, "Unable to create metal device");
|
||||||
if (!gfxApi.compare("OpenGL"))
|
m_metalCtx.m_q = [m_metalCtx.m_dev newCommandQueue];
|
||||||
useGL = true;
|
while (![m_metalCtx.m_dev supportsTextureSampleCount:m_metalCtx.m_sampleCount])
|
||||||
for (const SystemString& arg : args)
|
m_metalCtx.m_sampleCount = flp2(m_metalCtx.m_sampleCount - 1);
|
||||||
{
|
Log.report(logvisor::Info, "using Metal renderer");
|
||||||
if (!arg.compare("--gl"))
|
|
||||||
{
|
|
||||||
useGL = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else if (!arg.compare("--metal"))
|
|
||||||
{
|
|
||||||
useGL = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!useGL)
|
|
||||||
m_metalCtx.m_dev = MTLCreateSystemDefaultDevice();
|
|
||||||
if (m_metalCtx.m_dev)
|
|
||||||
{
|
|
||||||
m_metalCtx.m_q = [m_metalCtx.m_dev newCommandQueue];
|
|
||||||
while (![m_metalCtx.m_dev supportsTextureSampleCount:m_metalCtx.m_sampleCount])
|
|
||||||
m_metalCtx.m_sampleCount = flp2(m_metalCtx.m_sampleCount - 1);
|
|
||||||
Log.report(logvisor::Info, "using Metal renderer");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
Log.report(logvisor::Info, "using OpenGL renderer");
|
|
||||||
#else
|
|
||||||
Log.report(logvisor::Info, "using OpenGL renderer");
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
EPlatformType getPlatformType() const
|
EPlatformType getPlatformType() const
|
||||||
|
@ -214,19 +191,23 @@ public:
|
||||||
|
|
||||||
std::shared_ptr<IWindow> newWindow(std::string_view title)
|
std::shared_ptr<IWindow> newWindow(std::string_view title)
|
||||||
{
|
{
|
||||||
auto newWindow = _WindowCocoaNew(title, m_lastGLCtx, &m_metalCtx, &m_glCtx);
|
auto newWindow = _WindowCocoaNew(title, &m_metalCtx);
|
||||||
m_windows[newWindow->getPlatformHandle()] = newWindow;
|
m_windows[newWindow->getPlatformHandle()] = newWindow;
|
||||||
return newWindow;
|
return newWindow;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Last GL context */
|
/* Last GL context */
|
||||||
|
#if 0
|
||||||
NSOpenGLContext* m_lastGLCtx = nullptr;
|
NSOpenGLContext* m_lastGLCtx = nullptr;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#if 0
|
||||||
void _CocoaUpdateLastGLCtx(NSOpenGLContext* lastGLCtx)
|
void _CocoaUpdateLastGLCtx(NSOpenGLContext* lastGLCtx)
|
||||||
{
|
{
|
||||||
static_cast<ApplicationCocoa*>(APP)->m_lastGLCtx = lastGLCtx;
|
static_cast<ApplicationCocoa*>(APP)->m_lastGLCtx = lastGLCtx;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
IApplication* APP = nullptr;
|
IApplication* APP = nullptr;
|
||||||
int ApplicationRun(IApplication::EPlatformType platform,
|
int ApplicationRun(IApplication::EPlatformType platform,
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
#include "boo/graphicsdev/GL.hpp"
|
|
||||||
#include "boo/graphicsdev/glew.h"
|
|
||||||
#include "boo/graphicsdev/Metal.hpp"
|
#include "boo/graphicsdev/Metal.hpp"
|
||||||
#include "CocoaCommon.hpp"
|
#include "CocoaCommon.hpp"
|
||||||
#import <AppKit/AppKit.h>
|
#import <AppKit/AppKit.h>
|
||||||
|
@ -26,6 +24,7 @@ namespace boo {class WindowCocoa; class GraphicsContextCocoa;}
|
||||||
- (void)setTouchBarProvider:(id)provider;
|
- (void)setTouchBarProvider:(id)provider;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
#if 0
|
||||||
/* AppKit applies OpenGL much differently than other platforms
|
/* AppKit applies OpenGL much differently than other platforms
|
||||||
* the NSOpenGLView class composes together all necessary
|
* the NSOpenGLView class composes together all necessary
|
||||||
* OGL context members and provides the necessary event hooks
|
* OGL context members and provides the necessary event hooks
|
||||||
|
@ -95,6 +94,7 @@ static const NSOpenGLPixelFormatAttribute* PF_TABLE[] =
|
||||||
PF_RGBAF32_ATTRS,
|
PF_RGBAF32_ATTRS,
|
||||||
PF_RGBAF32_Z24_ATTRS
|
PF_RGBAF32_Z24_ATTRS
|
||||||
};
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
@interface BooCocoaResponder : NSResponder <NSTextInputClient>
|
@interface BooCocoaResponder : NSResponder <NSTextInputClient>
|
||||||
{
|
{
|
||||||
|
@ -155,10 +155,13 @@ public:
|
||||||
}
|
}
|
||||||
virtual BooCocoaResponder* responder() const=0;
|
virtual BooCocoaResponder* responder() const=0;
|
||||||
};
|
};
|
||||||
|
#if 0
|
||||||
class GraphicsContextCocoaGL;
|
class GraphicsContextCocoaGL;
|
||||||
|
#endif
|
||||||
class GraphicsContextCocoaMetal;
|
class GraphicsContextCocoaMetal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
@interface GraphicsContextCocoaGLInternal : NSOpenGLView
|
@interface GraphicsContextCocoaGLInternal : NSOpenGLView
|
||||||
{
|
{
|
||||||
@public
|
@public
|
||||||
|
@ -166,6 +169,7 @@ class GraphicsContextCocoaMetal;
|
||||||
}
|
}
|
||||||
- (id)initWithBooContext:(boo::GraphicsContextCocoaGL*)bctx;
|
- (id)initWithBooContext:(boo::GraphicsContextCocoaGL*)bctx;
|
||||||
@end
|
@end
|
||||||
|
#endif
|
||||||
|
|
||||||
@interface GraphicsContextCocoaMetalInternal : NSView
|
@interface GraphicsContextCocoaMetalInternal : NSView
|
||||||
{
|
{
|
||||||
|
@ -181,11 +185,14 @@ class GraphicsContextCocoaMetal;
|
||||||
namespace boo
|
namespace boo
|
||||||
{
|
{
|
||||||
static logvisor::Module Log("boo::WindowCocoa");
|
static logvisor::Module Log("boo::WindowCocoa");
|
||||||
|
#if 0
|
||||||
std::unique_ptr<IGraphicsCommandQueue> _NewGLCommandQueue(IGraphicsContext* parent, GLContext* glCtx);
|
std::unique_ptr<IGraphicsCommandQueue> _NewGLCommandQueue(IGraphicsContext* parent, GLContext* glCtx);
|
||||||
std::unique_ptr<IGraphicsDataFactory> _NewGLDataFactory(IGraphicsContext* parent, GLContext* glCtx);
|
std::unique_ptr<IGraphicsDataFactory> _NewGLDataFactory(IGraphicsContext* parent, GLContext* glCtx);
|
||||||
|
#endif
|
||||||
std::unique_ptr<IGraphicsCommandQueue> _NewMetalCommandQueue(MetalContext* ctx, IWindow* parentWindow,
|
std::unique_ptr<IGraphicsCommandQueue> _NewMetalCommandQueue(MetalContext* ctx, IWindow* parentWindow,
|
||||||
IGraphicsContext* parent);
|
IGraphicsContext* parent);
|
||||||
std::unique_ptr<IGraphicsDataFactory> _NewMetalDataFactory(IGraphicsContext* parent, MetalContext* ctx);
|
std::unique_ptr<IGraphicsDataFactory> _NewMetalDataFactory(IGraphicsContext* parent, MetalContext* ctx);
|
||||||
|
#if 0
|
||||||
void _CocoaUpdateLastGLCtx(NSOpenGLContext* lastGLCtx);
|
void _CocoaUpdateLastGLCtx(NSOpenGLContext* lastGLCtx);
|
||||||
|
|
||||||
class GraphicsContextCocoaGL : public GraphicsContextCocoa
|
class GraphicsContextCocoaGL : public GraphicsContextCocoa
|
||||||
|
@ -349,6 +356,7 @@ IGraphicsContext* _GraphicsContextCocoaGLNew(IGraphicsContext::EGraphicsAPI api,
|
||||||
|
|
||||||
return new GraphicsContextCocoaGL(api, parentWindow, lastGLCtx, glCtx);
|
return new GraphicsContextCocoaGL(api, parentWindow, lastGLCtx, glCtx);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#if BOO_HAS_METAL
|
#if BOO_HAS_METAL
|
||||||
class GraphicsContextCocoaMetal : public GraphicsContextCocoa
|
class GraphicsContextCocoaMetal : public GraphicsContextCocoa
|
||||||
|
@ -1147,6 +1155,7 @@ static boo::ESpecialKey translateKeycode(short code)
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
#if 0
|
||||||
@implementation GraphicsContextCocoaGLInternal
|
@implementation GraphicsContextCocoaGLInternal
|
||||||
- (id)initWithBooContext:(boo::GraphicsContextCocoaGL*)bctx
|
- (id)initWithBooContext:(boo::GraphicsContextCocoaGL*)bctx
|
||||||
{
|
{
|
||||||
|
@ -1203,6 +1212,7 @@ static boo::ESpecialKey translateKeycode(short code)
|
||||||
}
|
}
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
#endif
|
||||||
|
|
||||||
#if BOO_HAS_METAL
|
#if BOO_HAS_METAL
|
||||||
@implementation GraphicsContextCocoaMetalInternal
|
@implementation GraphicsContextCocoaMetalInternal
|
||||||
|
@ -1299,23 +1309,16 @@ class WindowCocoa : public IWindow
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
void setup(std::string_view title, NSOpenGLContext* lastGLCtx, MetalContext* metalCtx, GLContext* glCtx)
|
void setup(std::string_view title, MetalContext* metalCtx)
|
||||||
{
|
{
|
||||||
dispatch_sync(dispatch_get_main_queue(),
|
dispatch_sync(dispatch_get_main_queue(),
|
||||||
^{
|
^{
|
||||||
std::shared_ptr<boo::WindowCocoa> windowPtr =
|
std::shared_ptr<boo::WindowCocoa> windowPtr =
|
||||||
std::static_pointer_cast<boo::WindowCocoa>(shared_from_this());
|
std::static_pointer_cast<boo::WindowCocoa>(shared_from_this());
|
||||||
m_nsWindow = [[WindowCocoaInternal alloc] initWithBooWindow:windowPtr title:title];
|
m_nsWindow = [[WindowCocoaInternal alloc] initWithBooWindow:windowPtr title:title];
|
||||||
#if BOO_HAS_METAL
|
m_gfxCtx = static_cast<GraphicsContextCocoa*>(
|
||||||
if (metalCtx->m_dev)
|
_GraphicsContextCocoaMetalNew(IGraphicsContext::EGraphicsAPI::Metal,
|
||||||
m_gfxCtx = static_cast<GraphicsContextCocoa*>(
|
this, metalCtx));
|
||||||
_GraphicsContextCocoaMetalNew(IGraphicsContext::EGraphicsAPI::Metal,
|
|
||||||
this, metalCtx));
|
|
||||||
else
|
|
||||||
#endif
|
|
||||||
m_gfxCtx = static_cast<GraphicsContextCocoa*>(
|
|
||||||
_GraphicsContextCocoaGLNew(IGraphicsContext::EGraphicsAPI::OpenGL3_3,
|
|
||||||
this, lastGLCtx, glCtx));
|
|
||||||
m_gfxCtx->initializeContext(nullptr);
|
m_gfxCtx->initializeContext(nullptr);
|
||||||
});
|
});
|
||||||
m_gfxCtx->getMainContextDataFactory();
|
m_gfxCtx->getMainContextDataFactory();
|
||||||
|
@ -1587,11 +1590,10 @@ public:
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
std::shared_ptr<IWindow> _WindowCocoaNew(SystemStringView title, NSOpenGLContext* lastGLCtx,
|
std::shared_ptr<IWindow> _WindowCocoaNew(SystemStringView title, MetalContext* metalCtx)
|
||||||
MetalContext* metalCtx, GLContext* glCtx)
|
|
||||||
{
|
{
|
||||||
auto ret = std::make_shared<WindowCocoa>();
|
auto ret = std::make_shared<WindowCocoa>();
|
||||||
ret->setup(title, lastGLCtx, metalCtx, glCtx);
|
ret->setup(title, metalCtx);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue