From fc513e163bd08716a0ec5cd83862ddaabd7e167e Mon Sep 17 00:00:00 2001 From: Richard Schubert Date: Tue, 5 Oct 2021 18:01:50 +0200 Subject: [PATCH] Add new Example - AMD FidelityFX-FSR (#2612) * Add new sample * Add FSR shaders * Add example template * Add Antialiasing * Add multi resolution rendering * Implement magnifier * Implement FSR EASU pass * Implement FSR RCAS pass * Improve wording of comments and UI * Remove use of ffx_a.h in cpp * Remove example external files * Perform bilinear upsampling by compute shader * Add FSR 16 Bit support * Improve magnifier picking * Fix magnifier picking * Render magnifier widget * Renaming of stuff * Separate magnifier functionality * Move FSR into separate class * Reduce scope of FSR resources * Fix FSR for Vulkan * Fix OpenGL support * Update sample screenshot --- examples/46-fsr/app.cpp | 768 +++++ examples/46-fsr/cs_fsr.h | 129 + examples/46-fsr/cs_fsr_bilinear_16.sc | 3 + examples/46-fsr/cs_fsr_bilinear_32.sc | 4 + examples/46-fsr/cs_fsr_easu_16.sc | 3 + examples/46-fsr/cs_fsr_easu_32.sc | 4 + examples/46-fsr/cs_fsr_rcas_16.sc | 3 + examples/46-fsr/cs_fsr_rcas_32.sc | 4 + examples/46-fsr/ffx_a.h | 2637 +++++++++++++++++ examples/46-fsr/ffx_fsr1.h | 1199 ++++++++ .../46-fsr/fs_fsr_copy_linear_to_gamma.sc | 21 + examples/46-fsr/fs_fsr_forward.sc | 79 + examples/46-fsr/fs_fsr_forward_grid.sc | 58 + examples/46-fsr/fsr.cpp | 248 ++ examples/46-fsr/fsr.h | 47 + examples/46-fsr/makefile | 10 + examples/46-fsr/screenshot.png | Bin 0 -> 180092 bytes examples/46-fsr/varying.def.sc | 9 + examples/46-fsr/vs_fsr_forward.sc | 34 + examples/46-fsr/vs_fsr_screenquad.sc | 10 + 20 files changed, 5270 insertions(+) create mode 100644 examples/46-fsr/app.cpp create mode 100644 examples/46-fsr/cs_fsr.h create mode 100644 examples/46-fsr/cs_fsr_bilinear_16.sc create mode 100644 examples/46-fsr/cs_fsr_bilinear_32.sc create mode 100644 examples/46-fsr/cs_fsr_easu_16.sc create mode 100644 examples/46-fsr/cs_fsr_easu_32.sc create mode 100644 examples/46-fsr/cs_fsr_rcas_16.sc create mode 100644 examples/46-fsr/cs_fsr_rcas_32.sc create mode 100644 examples/46-fsr/ffx_a.h create mode 100644 examples/46-fsr/ffx_fsr1.h create mode 100644 examples/46-fsr/fs_fsr_copy_linear_to_gamma.sc create mode 100644 examples/46-fsr/fs_fsr_forward.sc create mode 100644 examples/46-fsr/fs_fsr_forward_grid.sc create mode 100644 examples/46-fsr/fsr.cpp create mode 100644 examples/46-fsr/fsr.h create mode 100644 examples/46-fsr/makefile create mode 100644 examples/46-fsr/screenshot.png create mode 100644 examples/46-fsr/varying.def.sc create mode 100644 examples/46-fsr/vs_fsr_forward.sc create mode 100644 examples/46-fsr/vs_fsr_screenquad.sc diff --git a/examples/46-fsr/app.cpp b/examples/46-fsr/app.cpp new file mode 100644 index 000000000..46f1a434d --- /dev/null +++ b/examples/46-fsr/app.cpp @@ -0,0 +1,768 @@ +/* +* Copyright 2021 Richard Schubert. All rights reserved. +* License: https://github.com/bkaradzic/bgfx#license-bsd-2-clause +* +* AMD FidelityFX Super Resolution 1.0 (FSR) +* Based on https://github.com/GPUOpen-Effects/FidelityFX-FSR/blob/master/sample/ +*/ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "fsr.h" + +namespace +{ + +#define FRAMEBUFFER_RT_COLOR 0 +#define FRAMEBUFFER_RT_DEPTH 1 +#define FRAMEBUFFER_RENDER_TARGETS 2 + + enum Meshes + { + MeshCube = 0, + MeshHollowCube + }; + + static const char *s_meshPaths[] = + { + "meshes/cube.bin", + "meshes/hollowcube.bin"}; + + static const float s_meshScale[] = + { + 0.45f, + 0.30f}; + + // Vertex decl for our screen space quad (used in deferred rendering) + struct PosTexCoord0Vertex + { + float m_x; + float m_y; + float m_z; + float m_u; + float m_v; + + static void init() + { + ms_layout + .begin() + .add(bgfx::Attrib::Position, 3, bgfx::AttribType::Float) + .add(bgfx::Attrib::TexCoord0, 2, bgfx::AttribType::Float) + .end(); + } + + static bgfx::VertexLayout ms_layout; + }; + + bgfx::VertexLayout PosTexCoord0Vertex::ms_layout; + + void screenSpaceTriangle(float _textureWidth, float _textureHeight, float _texelHalf, bool _originBottomLeft, float _width = 1.0f, float _height = 1.0f, float _offsetX = 0.0f, float _offsetY = 0.0f) + { + if (3 == bgfx::getAvailTransientVertexBuffer(3, PosTexCoord0Vertex::ms_layout)) + { + bgfx::TransientVertexBuffer vb; + bgfx::allocTransientVertexBuffer(&vb, 3, PosTexCoord0Vertex::ms_layout); + PosTexCoord0Vertex *vertex = (PosTexCoord0Vertex *)vb.data; + + const float minx = -_width - _offsetX; + const float maxx = _width - _offsetX; + const float miny = 0.0f - _offsetY; + const float maxy = _height * 2.0f - _offsetY; + + const float texelHalfW = _texelHalf / _textureWidth; + const float texelHalfH = _texelHalf / _textureHeight; + const float minu = -1.0f + texelHalfW; + const float maxu = 1.0f + texelHalfW; + + const float zz = 0.0f; + + float minv = texelHalfH; + float maxv = 2.0f + texelHalfH; + + if (_originBottomLeft) + { + float temp = minv; + minv = maxv; + maxv = temp; + + minv -= 1.0f; + maxv -= 1.0f; + } + + vertex[0].m_x = minx; + vertex[0].m_y = miny; + vertex[0].m_z = zz; + vertex[0].m_u = minu; + vertex[0].m_v = minv; + + vertex[1].m_x = maxx; + vertex[1].m_y = miny; + vertex[1].m_z = zz; + vertex[1].m_u = maxu; + vertex[1].m_v = minv; + + vertex[2].m_x = maxx; + vertex[2].m_y = maxy; + vertex[2].m_z = zz; + vertex[2].m_u = maxu; + vertex[2].m_v = maxv; + + bgfx::setVertexBuffer(0, &vb); + } + } + + struct ModelUniforms + { + enum + { + NumVec4 = 2 + }; + + void init() + { + u_params = bgfx::createUniform("u_modelParams", bgfx::UniformType::Vec4, NumVec4); + }; + + void submit() const + { + bgfx::setUniform(u_params, m_params, NumVec4); + }; + + void destroy() + { + bgfx::destroy(u_params); + } + + union + { + struct + { + /* 0 */ struct + { + float m_color[3]; + float m_unused0; + }; + /* 1 */ struct + { + float m_lightPosition[3]; + float m_unused1; + }; + }; + + float m_params[NumVec4 * 4]; + }; + + bgfx::UniformHandle u_params; + }; + + struct AppState + { + uint32_t m_width; + uint32_t m_height; + uint32_t m_debug; + uint32_t m_reset; + + entry::MouseState m_mouseState; + + // Resource handles + bgfx::ProgramHandle m_forwardProgram; + bgfx::ProgramHandle m_gridProgram; + bgfx::ProgramHandle m_copyLinearToGammaProgram; + + // Shader uniforms + ModelUniforms m_modelUniforms; + + // Uniforms to indentify texture samplers + bgfx::UniformHandle s_albedo; + bgfx::UniformHandle s_color; + bgfx::UniformHandle s_normal; + + bgfx::FrameBufferHandle m_frameBuffer; + bgfx::TextureHandle m_frameBufferTex[FRAMEBUFFER_RENDER_TARGETS]; + + Mesh *m_meshes[BX_COUNTOF(s_meshPaths)]; + bgfx::TextureHandle m_groundTexture; + bgfx::TextureHandle m_normalTexture; + + uint32_t m_currFrame{UINT32_MAX}; + float m_lightRotation = 0.0f; + float m_texelHalf = 0.0f; + float m_fovY = 60.0f; + float m_animationTime = 0.0f; + + float m_view[16]; + float m_proj[16]; + int32_t m_size[2]; + + // UI parameters + bool m_renderNativeResolution = false; + bool m_animateScene = false; + int32_t m_antiAliasingSetting = 2; + + Fsr m_fsr; + }; + + struct RenderTarget + { + void init(uint32_t _width, uint32_t _height, bgfx::TextureFormat::Enum _format, uint64_t _flags) + { + m_width = _width; + m_height = _height; + m_texture = bgfx::createTexture2D(uint16_t(_width), uint16_t(_height), false, 1, _format, _flags); + const bool destroyTextures = true; + m_buffer = bgfx::createFrameBuffer(1, &m_texture, destroyTextures); + } + + void destroy() + { + // also responsible for destroying texture + bgfx::destroy(m_buffer); + } + + uint32_t m_width; + uint32_t m_height; + bgfx::TextureHandle m_texture; + bgfx::FrameBufferHandle m_buffer; + }; + + struct MagnifierWidget + { + void init(uint32_t _width, uint32_t _height) + { + m_content.init(_width, _height, bgfx::TextureFormat::BGRA8, BGFX_TEXTURE_RT | BGFX_SAMPLER_MIN_POINT | BGFX_SAMPLER_MAG_POINT); + createWidgetTexture(_width + 6, _height + 6); + } + + void destroy() + { + bgfx::destroy(m_widgetTexture); + m_content.destroy(); + } + + void setPosition(float x, float y) + { + m_position.x = x; + m_position.y = y; + } + + void drawToScreen(bgfx::ViewId &view, AppState const &state, const bgfx::Caps *caps) + { + float invScreenScaleX = 1.0f / static_cast(state.m_width); + float invScreenScaleY = 1.0f / static_cast(state.m_height); + float scaleX = m_widgetWidth * invScreenScaleX; + float scaleY = m_widgetHeight * invScreenScaleY; + float offsetX = -std::min(std::max(m_position.x - m_widgetWidth * 0.5f, -3.0f), static_cast(state.m_width - m_widgetWidth + 3)) * invScreenScaleX; + float offsetY = -std::min(std::max(m_position.y - m_widgetHeight * 0.5f, -3.0f), static_cast(state.m_height - m_widgetHeight + 3)) * invScreenScaleY; + + bgfx::setState(0 | BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A | BGFX_STATE_DEPTH_TEST_ALWAYS | BGFX_STATE_BLEND_ALPHA); + bgfx::setTexture(0, state.s_color, m_widgetTexture); + screenSpaceTriangle(float(m_widgetWidth), float(m_widgetHeight), state.m_texelHalf, false, scaleX, scaleY, offsetX, offsetY); + bgfx::submit(view, state.m_copyLinearToGammaProgram); + } + + void updateContent(bgfx::ViewId &view, AppState const &state, const bgfx::Caps *caps, bgfx::TextureHandle srcTexture) + { + float orthoProj[16]; + bx::mtxOrtho(orthoProj, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, caps->homogeneousDepth); + { + // clear out transform stack + float identity[16]; + bx::mtxIdentity(identity); + bgfx::setTransform(identity); + } + + float const verticalPos = caps->originBottomLeft ? state.m_height - m_position.y : m_position.y; + float const invMagScaleX = 1.0f / static_cast(m_content.m_width); + float const invMagScaleY = 1.0f / static_cast(m_content.m_height); + float const scaleX = state.m_width * invMagScaleX; + float const scaleY = state.m_height * invMagScaleY; + float const offsetX = std::min(std::max(m_position.x - m_content.m_width * 0.5f, 0.0f), static_cast(state.m_width - m_content.m_width)) * scaleX / state.m_width; + float const offsetY = std::min(std::max(verticalPos - m_content.m_height * 0.5f, 0.0f), static_cast(state.m_height - m_content.m_height)) * scaleY / state.m_height; + + bgfx::setViewName(view, "magnifier"); + bgfx::setViewRect(view, 0, 0, uint16_t(m_content.m_width), uint16_t(m_content.m_height)); + bgfx::setViewTransform(view, NULL, orthoProj); + bgfx::setViewFrameBuffer(view, m_content.m_buffer); + bgfx::setState(0 | BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A); + bgfx::setTexture(0, state.s_color, srcTexture, BGFX_SAMPLER_MIN_POINT | BGFX_SAMPLER_MAG_POINT | BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP); + screenSpaceTriangle(float(state.m_width), float(state.m_height), state.m_texelHalf, false, scaleX, scaleY, offsetX, offsetY); + bgfx::submit(view, state.m_copyLinearToGammaProgram); + ++view; + } + + uint32_t m_widgetWidth{0}; + uint32_t m_widgetHeight{0}; + bgfx::TextureHandle m_widgetTexture; + RenderTarget m_content; + ImVec2 m_position; + + private: + void createWidgetTexture(uint32_t _width, uint32_t _height) + { + const bgfx::Memory *mem = bgfx::alloc(_width * _height * sizeof(uint32_t)); + + uint32_t *pixels = const_cast((uint32_t *const)(mem->data)); + memset(pixels, 0, mem->size); + + uint32_t const white = 0xFFFFFFFF; + uint32_t const black = 0xFF000000; + + uint32_t const y0 = 1; + uint32_t const y1 = _height - 3; + for (uint32_t x = 0; x < _width - 4; x++) + { + pixels[(y0 + 0) * _width + x + 1] = white; + pixels[(y0 + 1) * _width + x + 2] = black; + pixels[(y1 + 0) * _width + x + 1] = white; + pixels[(y1 + 1) * _width + x + 2] = black; + } + uint32_t const x0 = 1; + uint32_t const x1 = _width - 3; + for (uint32_t y = 0; y < _height - 3; y++) + { + pixels[(y + 1) * _width + x0 + 0] = white; + pixels[(y + 2) * _width + x0 + 1] = black; + pixels[(y + 1) * _width + x1 + 0] = white; + pixels[(y + 2) * _width + x1 + 1] = black; + } + pixels[(y1 + 0) * _width + 2] = white; + + m_widgetWidth = _width; + m_widgetHeight = _height; + m_widgetTexture = bgfx::createTexture2D(uint16_t(_width), uint16_t(_height), false, 1, bgfx::TextureFormat::BGRA8, BGFX_SAMPLER_MIN_POINT | BGFX_SAMPLER_MAG_POINT | BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP, mem); + } + }; + + class ExampleFsr : public entry::AppI + { + public: + ExampleFsr(const char *_name, const char *_description) + : entry::AppI(_name, _description) + { + } + + void init(int32_t _argc, const char *const *_argv, uint32_t _width, uint32_t _height) override + { + Args args(_argc, _argv); + + m_state.m_width = _width; + m_state.m_height = _height; + m_state.m_debug = BGFX_DEBUG_NONE; + m_state.m_reset = BGFX_RESET_MAXANISOTROPY; + + bgfx::Init init; + init.type = args.m_type; + + init.vendorId = args.m_pciId; + init.resolution.width = m_state.m_width; + init.resolution.height = m_state.m_height; + init.resolution.reset = m_state.m_reset; + bgfx::init(init); + + // Enable debug text. + bgfx::setDebug(m_state.m_debug); + + // Create uniforms for screen passes and models + m_state.m_modelUniforms.init(); + + // Create texture sampler uniforms (used when we bind textures) + m_state.s_albedo = bgfx::createUniform("s_albedo", bgfx::UniformType::Sampler); + m_state.s_color = bgfx::createUniform("s_color", bgfx::UniformType::Sampler); + m_state.s_normal = bgfx::createUniform("s_normal", bgfx::UniformType::Sampler); + + // Create program from shaders. + m_state.m_forwardProgram = loadProgram("vs_fsr_forward", "fs_fsr_forward"); + m_state.m_gridProgram = loadProgram("vs_fsr_forward", "fs_fsr_forward_grid"); + m_state.m_copyLinearToGammaProgram = loadProgram("vs_fsr_screenquad", "fs_fsr_copy_linear_to_gamma"); + + // Load some meshes + for (uint32_t ii = 0; ii < BX_COUNTOF(s_meshPaths); ++ii) + { + m_state.m_meshes[ii] = meshLoad(s_meshPaths[ii]); + } + + m_state.m_groundTexture = loadTexture("textures/fieldstone-rgba.dds"); + m_state.m_normalTexture = loadTexture("textures/fieldstone-n.dds"); + + createFramebuffers(); + + // Vertex decl + PosTexCoord0Vertex::init(); + + // Init camera + cameraCreate(); + cameraSetPosition({-10.0f, 2.5f, -0.0f}); + cameraSetVerticalAngle(-0.2f); + cameraSetHorizontalAngle(0.8f); + + // Init "prev" matrices, will be same for first frame + cameraGetViewMtx(m_state.m_view); + bx::mtxProj(m_state.m_proj, m_state.m_fovY, float(m_state.m_size[0]) / float(m_state.m_size[1]), 0.01f, 100.0f, bgfx::getCaps()->homogeneousDepth); + + // Get renderer capabilities info. + const bgfx::RendererType::Enum renderer = bgfx::getRendererType(); + m_state.m_texelHalf = bgfx::RendererType::Direct3D9 == renderer ? 0.5f : 0.0f; + + const uint32_t magnifierSize = 32; + m_magnifierWidget.init(magnifierSize, magnifierSize); + m_magnifierWidget.setPosition(m_state.m_width * 0.5f, m_state.m_height * 0.5f); + + imguiCreate(); + + m_state.m_fsr.init(_width, _height); + } + + int32_t shutdown() override + { + m_state.m_fsr.destroy(); + + for (uint32_t ii = 0; ii < BX_COUNTOF(s_meshPaths); ++ii) + { + meshUnload(m_state.m_meshes[ii]); + } + + bgfx::destroy(m_state.m_normalTexture); + bgfx::destroy(m_state.m_groundTexture); + + bgfx::destroy(m_state.m_forwardProgram); + bgfx::destroy(m_state.m_gridProgram); + bgfx::destroy(m_state.m_copyLinearToGammaProgram); + + m_state.m_modelUniforms.destroy(); + + m_magnifierWidget.destroy(); + + bgfx::destroy(m_state.s_albedo); + bgfx::destroy(m_state.s_color); + bgfx::destroy(m_state.s_normal); + + destroyFramebuffers(); + + cameraDestroy(); + + imguiDestroy(); + + bgfx::shutdown(); + + return 0; + } + + bool update() override + { + if (!entry::processEvents(m_state.m_width, m_state.m_height, m_state.m_debug, m_state.m_reset, &m_state.m_mouseState)) + { + // skip processing when minimized, otherwise crashing + if (0 == m_state.m_width || 0 == m_state.m_height) + { + return true; + } + + if (m_state.m_mouseState.m_buttons[entry::MouseButton::Left] && !ImGui::MouseOverArea()) + { + m_magnifierWidget.setPosition(static_cast(m_state.m_mouseState.m_mx), + static_cast(m_state.m_mouseState.m_my)); + } + + // Update frame timer + int64_t now = bx::getHPCounter(); + static int64_t last = now; + const int64_t frameTime = now - last; + last = now; + const double freq = double(bx::getHPFrequency()); + const float deltaTime = float(frameTime / freq); + const bgfx::Caps *caps = bgfx::getCaps(); + + if (m_state.m_size[0] != (int32_t)m_state.m_width || m_state.m_size[1] != (int32_t)m_state.m_height) + { + resize(); + } + + // update animation time + const float rotationSpeed = 0.25f; + if (m_state.m_animateScene) + { + m_state.m_animationTime += deltaTime * rotationSpeed; + if (bx::kPi2 < m_state.m_animationTime) + { + m_state.m_animationTime -= bx::kPi2; + } + } + + // Update camera + cameraUpdate(deltaTime * 0.15f, m_state.m_mouseState, ImGui::MouseOverArea()); + + cameraGetViewMtx(m_state.m_view); + + updateUniforms(); + + bx::mtxProj(m_state.m_proj, m_state.m_fovY, float(m_state.m_size[0]) / float(m_state.m_size[1]), 0.01f, 100.0f, caps->homogeneousDepth); + + bgfx::ViewId view = 0; + + // Draw models into scene + { + bgfx::setViewName(view, "forward scene"); + bgfx::setViewClear(view, BGFX_CLEAR_COLOR | BGFX_CLEAR_DEPTH, 0x7fb8ffff, 1.0f, 0); + + float const viewScale = m_state.m_renderNativeResolution ? 1.0f : 1.0f / m_state.m_fsr.m_config.m_superSamplingFactor; + uint16_t const viewRectWidth = uint16_t(ceilf(m_state.m_size[0] * viewScale)); + uint16_t const viewRectHeight = uint16_t(ceilf(m_state.m_size[1] * viewScale)); + uint16_t const viewRectY = caps->originBottomLeft ? m_state.m_size[1] - viewRectHeight : 0; + bgfx::setViewRect(view, 0, viewRectY, viewRectWidth, viewRectHeight); + bgfx::setViewTransform(view, m_state.m_view, m_state.m_proj); + bgfx::setViewFrameBuffer(view, m_state.m_frameBuffer); + + bgfx::setState(0 | BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A | BGFX_STATE_WRITE_Z | BGFX_STATE_DEPTH_TEST_LESS); + + drawAllModels(view, m_state.m_forwardProgram, m_state.m_modelUniforms); + + ++view; + } + + // optionally run FSR + if (!m_state.m_renderNativeResolution) + { + view = m_state.m_fsr.computeFsr(view, m_state.m_frameBufferTex[FRAMEBUFFER_RT_COLOR]); + } + + // render result to screen + { + bgfx::TextureHandle srcTexture = m_state.m_frameBufferTex[FRAMEBUFFER_RT_COLOR]; + if (!m_state.m_renderNativeResolution) + { + srcTexture = m_state.m_fsr.getResultTexture(); + } + + m_magnifierWidget.updateContent(view, m_state, caps, srcTexture); + + float orthoProj[16]; + bx::mtxOrtho(orthoProj, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f, caps->homogeneousDepth); + { + // clear out transform stack + float identity[16]; + bx::mtxIdentity(identity); + bgfx::setTransform(identity); + } + + bgfx::setViewName(view, "display"); + bgfx::setViewClear(view, BGFX_CLEAR_NONE, 0, 1.0f, 0); + + bgfx::setViewRect(view, 0, 0, uint16_t(m_state.m_width), uint16_t(m_state.m_height)); + bgfx::setViewTransform(view, NULL, orthoProj); + bgfx::setViewFrameBuffer(view, BGFX_INVALID_HANDLE); + bgfx::setState(0 | BGFX_STATE_WRITE_RGB | BGFX_STATE_WRITE_A); + bgfx::setTexture(0, m_state.s_color, srcTexture, BGFX_SAMPLER_MIN_POINT | BGFX_SAMPLER_MAG_POINT | BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP); + screenSpaceTriangle(float(m_state.m_width), float(m_state.m_height), m_state.m_texelHalf, caps->originBottomLeft); + bgfx::submit(view, m_state.m_copyLinearToGammaProgram); + } + + m_magnifierWidget.drawToScreen(view, m_state, caps); + + ++view; + + // Draw UI + imguiBeginFrame(m_state.m_mouseState.m_mx, m_state.m_mouseState.m_my, (m_state.m_mouseState.m_buttons[entry::MouseButton::Left] ? IMGUI_MBUT_LEFT : 0) | (m_state.m_mouseState.m_buttons[entry::MouseButton::Right] ? IMGUI_MBUT_RIGHT : 0) | (m_state.m_mouseState.m_buttons[entry::MouseButton::Middle] ? IMGUI_MBUT_MIDDLE : 0), m_state.m_mouseState.m_mz, uint16_t(m_state.m_width), uint16_t(m_state.m_height)); + + showExampleDialog(this); + + ImGui::SetNextWindowPos(ImVec2(m_state.m_width - m_state.m_width / 4.0f - 10.0f, 10.0f), ImGuiCond_FirstUseEver); + ImGui::SetNextWindowSize(ImVec2(m_state.m_width / 4.0f, m_state.m_height / 1.2f), ImGuiCond_FirstUseEver); + ImGui::Begin("Settings", NULL, 0); + ImGui::PushItemWidth(ImGui::GetWindowWidth() * 0.5f); + + ImVec2 const itemSize = ImGui::GetItemRectSize(); + + { + ImGui::Checkbox("Animate scene", &m_state.m_animateScene); + + if (ImGui::Combo("Antialiasing", &m_state.m_antiAliasingSetting, "none\0" + "4x\0" + "16x\0" + "\0")) + { + resize(); + } + + ImGui::Checkbox("Render native resolution", &m_state.m_renderNativeResolution); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Disable super sampling and FSR."); + + ImGui::Image(m_magnifierWidget.m_content.m_texture, ImVec2(itemSize.x * 0.94f, itemSize.x * 0.94f)); + + if (!m_state.m_renderNativeResolution) + { + ImGui::SliderFloat("Super sampling", &m_state.m_fsr.m_config.m_superSamplingFactor, 1.0f, 2.0f); + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::Text("2.0 means the scene is rendered at half window resolution."); + ImGui::Text("1.0 means the scene is rendered at native window resolution."); + ImGui::EndTooltip(); + } + + ImGui::Separator(); + + if (m_state.m_fsr.supports16BitPrecision()) + { + ImGui::Checkbox("Use 16 Bit", &m_state.m_fsr.m_config.m_fsr16Bit); + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::Text("For better performance and less memory consumption use 16 Bit precision."); + ImGui::Text("If disabled use 32 Bit per channel precision for FSR which works better on older hardware."); + ImGui::Text("FSR in 16 Bit precision is also prone to be broken in Direct3D11, Direct3D12 works though."); + ImGui::EndTooltip(); + } + } + + ImGui::Checkbox("Apply FSR", &m_state.m_fsr.m_config.m_applyFsr); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Compare between FSR and bilinear interpolation of source image."); + + if (m_state.m_fsr.m_config.m_applyFsr) + { + ImGui::Checkbox("Apply FSR sharpening", &m_state.m_fsr.m_config.m_applyFsrRcas); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Apply the FSR RCAS sharpening pass."); + + if (m_state.m_fsr.m_config.m_applyFsrRcas) + { + ImGui::SliderFloat("Sharpening attenuation", &m_state.m_fsr.m_config.m_rcasAttenuation, 0.01f, 2.0f); + if (ImGui::IsItemHovered()) + ImGui::SetTooltip("Lower value means sharper."); + } + } + } + } + + ImGui::End(); + + imguiEndFrame(); + + // Advance to next frame. Rendering thread will be kicked to + // process submitted rendering primitives. + m_state.m_currFrame = bgfx::frame(); + + return true; + } + + return false; + } + + void drawAllModels(bgfx::ViewId _pass, bgfx::ProgramHandle _program, ModelUniforms &_uniforms) + { + const int32_t width = 6; + const int32_t length = 20; + + float c0[] = {235.0f / 255.0f, 126.0f / 255.0f, 30.0f / 255.0f}; // orange + float c1[] = {235.0f / 255.0f, 146.0f / 255.0f, 251.0f / 255.0f}; // purple + float c2[] = {199.0f / 255.0f, 0.0f / 255.0f, 57.0f / 255.0f}; // pink + + for (int32_t zz = 0; zz < length; ++zz) + { + // make a color gradient, nothing special about this for example + float *ca = c0; + float *cb = c1; + float lerpVal = float(zz) / float(length); + + if (0.5f <= lerpVal) + { + ca = c1; + cb = c2; + } + lerpVal = bx::fract(2.0f * lerpVal); + + float r = bx::lerp(ca[0], cb[0], lerpVal); + float g = bx::lerp(ca[1], cb[1], lerpVal); + float b = bx::lerp(ca[2], cb[2], lerpVal); + + for (int32_t xx = 0; xx < width; ++xx) + { + const float angle = m_state.m_animationTime + float(zz) * (bx::kPi2 / length) + float(xx) * (bx::kPiHalf / width); + + const float posX = 2.0f * xx - width + 1.0f; + const float posY = bx::sin(angle); + const float posZ = 2.0f * zz - length + 1.0f; + + const float scale = s_meshScale[MeshHollowCube]; + float mtx[16]; + bx::mtxSRT(mtx, scale, scale, scale, 0.0f, 0.0f, 0.0f, posX, posY, posZ); + + bgfx::setTexture(0, m_state.s_albedo, m_state.m_groundTexture); + bgfx::setTexture(1, m_state.s_normal, m_state.m_normalTexture); + _uniforms.m_color[0] = r; + _uniforms.m_color[1] = g; + _uniforms.m_color[2] = b; + _uniforms.submit(); + + meshSubmit(m_state.m_meshes[MeshHollowCube], _pass, _program, mtx); + } + } + + // draw box as ground plane + { + const float posY = -2.0f; + const float scale = length; + float mtx[16]; + bx::mtxSRT(mtx, scale, scale, scale, 0.0f, 0.0f, 0.0f, 0.0f, -scale + posY, 0.0f); + + _uniforms.m_color[0] = 0.5f; + _uniforms.m_color[1] = 0.5f; + _uniforms.m_color[2] = 0.5f; + _uniforms.submit(); + + meshSubmit(m_state.m_meshes[MeshCube], _pass, m_state.m_gridProgram, mtx); + } + } + + void resize() + { + destroyFramebuffers(); + createFramebuffers(); + m_state.m_fsr.resize(m_state.m_width, m_state.m_height); + } + + void createFramebuffers() + { + m_state.m_size[0] = m_state.m_width; + m_state.m_size[1] = m_state.m_height; + + uint64_t constexpr msaaFlags[] = {BGFX_TEXTURE_NONE, BGFX_TEXTURE_RT_MSAA_X4, BGFX_TEXTURE_RT_MSAA_X16}; + + const uint64_t msaa = msaaFlags[m_state.m_antiAliasingSetting]; + const uint64_t colorFlags = 0 | BGFX_TEXTURE_RT | BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP | msaa; + const uint64_t depthFlags = 0 | BGFX_TEXTURE_RT_WRITE_ONLY | msaa; + + m_state.m_frameBufferTex[FRAMEBUFFER_RT_COLOR] = bgfx::createTexture2D(uint16_t(m_state.m_size[0]), uint16_t(m_state.m_size[1]), false, 1, bgfx::TextureFormat::RGBA16F, colorFlags); + m_state.m_frameBufferTex[FRAMEBUFFER_RT_DEPTH] = bgfx::createTexture2D(uint16_t(m_state.m_size[0]), uint16_t(m_state.m_size[1]), false, 1, bgfx::TextureFormat::D24S8, depthFlags); + m_state.m_frameBuffer = bgfx::createFrameBuffer(BX_COUNTOF(m_state.m_frameBufferTex), m_state.m_frameBufferTex, true); + } + + // all buffers set to destroy their textures + void destroyFramebuffers() + { + bgfx::destroy(m_state.m_frameBuffer); + } + + void updateUniforms() + { + m_state.m_modelUniforms.m_lightPosition[0] = 0.0f; + m_state.m_modelUniforms.m_lightPosition[1] = 6.0f; + m_state.m_modelUniforms.m_lightPosition[2] = 10.0f; + } + + AppState m_state; + MagnifierWidget m_magnifierWidget; + }; + +} // namespace + +ENTRY_IMPLEMENT_MAIN(ExampleFsr, "46-fsr", "AMD FidelityFX Super Resolution (FSR)\n\nFor an optimal FSR result high quality antialiasing for the low resolution source image and negative texture LOD bias is recommended."); diff --git a/examples/46-fsr/cs_fsr.h b/examples/46-fsr/cs_fsr.h new file mode 100644 index 000000000..acb3a0dd6 --- /dev/null +++ b/examples/46-fsr/cs_fsr.h @@ -0,0 +1,129 @@ +/* +* Copyright 2021 Richard Schubert. All rights reserved. +* License: https://github.com/bkaradzic/bgfx#license-bsd-2-clause +* +* AMD FidelityFX Super Resolution 1.0 (FSR) +* Port from https://github.com/GPUOpen-Effects/FidelityFX-FSR/blob/master/sample/src/VK/FSR_Pass.glsl +*/ + +#include "bgfx_compute.sh" + +uniform vec4 u_params[3]; + +#define ViewportSizeRcasAttenuation (u_params[0]) +#define SrcSize (u_params[1]) +#define DstSize (u_params[2]) + +#define A_GPU 1 + +#if BGFX_SHADER_LANGUAGE_GLSL > 0 +#define A_GLSL 1 +#define A_SKIP_EXT 1 +#elif BGFX_SHADER_LANGUAGE_SPIRV > 0 +#define A_HLSL 1 +#elif BGFX_SHADER_LANGUAGE_HLSL > 0 +#define A_HLSL 1 +#endif + +#if SAMPLE_SLOW_FALLBACK + #include "ffx_a.h" + SAMPLER2D(InputTexture, 0); + IMAGE2D_WR(OutputTexture, rgba32f, 1); + #if SAMPLE_EASU + #define FSR_EASU_F 1 + AF4 FsrEasuRF(AF2 p) { AF4 res = textureGather(InputTexture, p, 0); return res; } + AF4 FsrEasuGF(AF2 p) { AF4 res = textureGather(InputTexture, p, 1); return res; } + AF4 FsrEasuBF(AF2 p) { AF4 res = textureGather(InputTexture, p, 2); return res; } + #endif + #if SAMPLE_RCAS + #define FSR_RCAS_F + AF4 FsrRcasLoadF(ASU2 p) { return texelFetch(InputTexture, ASU2(p), 0); } + void FsrRcasInputF(inout AF1 r, inout AF1 g, inout AF1 b) {} + #endif +#else + #define A_HALF + #include "ffx_a.h" + SAMPLER2D(InputTexture, 0); + IMAGE2D_WR(OutputTexture, rgba16f, 1); + #if SAMPLE_EASU + #define FSR_EASU_H 1 + AH4 FsrEasuRH(AF2 p) { AH4 res = AH4(textureGather(InputTexture, p, 0)); return res; } + AH4 FsrEasuGH(AF2 p) { AH4 res = AH4(textureGather(InputTexture, p, 1)); return res; } + AH4 FsrEasuBH(AF2 p) { AH4 res = AH4(textureGather(InputTexture, p, 2)); return res; } + #endif + #if SAMPLE_RCAS + #define FSR_RCAS_H + AH4 FsrRcasLoadH(ASW2 p) { return AH4(texelFetch(InputTexture, ASU2(p), 0)); } + void FsrRcasInputH(inout AH1 r,inout AH1 g,inout AH1 b){} + #endif +#endif + +#include "ffx_fsr1.h" + +void CurrFilter(AU2 pos, AU4 Const0, AU4 Const1, AU4 Const2, AU4 Const3, AU4 Sample) +{ +#if SAMPLE_BILINEAR + AF2 pp = (AF2(pos) * AF2_AU2(Const0.xy) + AF2_AU2(Const0.zw)) * AF2_AU2(Const1.xy) + AF2(0.5, -0.5) * AF2_AU2(Const1.zw); + imageStore(OutputTexture, ASU2(pos), texture2DLod(InputTexture, pp, 0.0)); +#endif +#if SAMPLE_EASU + #if SAMPLE_SLOW_FALLBACK + AF3 c; + FsrEasuF(c, pos, Const0, Const1, Const2, Const3); + if( Sample.x == 1 ) + c *= c; + imageStore(OutputTexture, ASU2(pos), AF4(c, 1)); + #else + AH3 c; + FsrEasuH(c, pos, Const0, Const1, Const2, Const3); + if( Sample.x == 1 ) + c *= c; + imageStore(OutputTexture, ASU2(pos), AH4(c, 1)); + #endif +#endif +#if SAMPLE_RCAS + #if SAMPLE_SLOW_FALLBACK + AF3 c; + FsrRcasF(c.r, c.g, c.b, pos, Const0); + if( Sample.x == 1 ) + c *= c; + imageStore(OutputTexture, ASU2(pos), AF4(c, 1)); + #else + AH3 c; + FsrRcasH(c.r, c.g, c.b, pos, Const0); + if( Sample.x == 1 ) + c *= c; + imageStore(OutputTexture, ASU2(pos), AH4(c, 1)); + #endif +#endif +} + +NUM_THREADS(64, 1, 1) +void main() +{ + AU4 Const0, Const1, Const2, Const3, Sample; + + // We compute these constants on GPU because bgfx does not support uniform type uint. +#if SAMPLE_EASU || SAMPLE_BILINEAR + FsrEasuCon(Const0, Const1, Const2, Const3, + ViewportSizeRcasAttenuation.x, ViewportSizeRcasAttenuation.y, // Viewport size (top left aligned) in the input image which is to be scaled. + SrcSize.x, SrcSize.y, // The size of the input image. + DstSize.x, DstSize.y); // The output resolution. + Sample.x = 0; // no HDR output +#endif +#if SAMPLE_RCAS + FsrRcasCon(Const0, ViewportSizeRcasAttenuation.z); + Sample.x = 0; // no HDR output +#endif + + // Do remapping of local xy in workgroup for a more PS-like swizzle pattern. + AU2 gxy = ARmp8x8(gl_LocalInvocationID.x) + AU2(gl_WorkGroupID.x << 4u, gl_WorkGroupID.y << 4u); + CurrFilter(gxy, Const0, Const1, Const2, Const3, Sample); + gxy.x += 8u; + CurrFilter(gxy, Const0, Const1, Const2, Const3, Sample); + gxy.y += 8u; + CurrFilter(gxy, Const0, Const1, Const2, Const3, Sample); + gxy.x -= 8u; + CurrFilter(gxy, Const0, Const1, Const2, Const3, Sample); +} + diff --git a/examples/46-fsr/cs_fsr_bilinear_16.sc b/examples/46-fsr/cs_fsr_bilinear_16.sc new file mode 100644 index 000000000..c6ca756cf --- /dev/null +++ b/examples/46-fsr/cs_fsr_bilinear_16.sc @@ -0,0 +1,3 @@ +#define SAMPLE_BILINEAR 1 + +#include "cs_fsr.h" diff --git a/examples/46-fsr/cs_fsr_bilinear_32.sc b/examples/46-fsr/cs_fsr_bilinear_32.sc new file mode 100644 index 000000000..c803a9ff9 --- /dev/null +++ b/examples/46-fsr/cs_fsr_bilinear_32.sc @@ -0,0 +1,4 @@ +#define SAMPLE_BILINEAR 1 +#define SAMPLE_SLOW_FALLBACK 1 + +#include "cs_fsr.h" diff --git a/examples/46-fsr/cs_fsr_easu_16.sc b/examples/46-fsr/cs_fsr_easu_16.sc new file mode 100644 index 000000000..8dc730f7f --- /dev/null +++ b/examples/46-fsr/cs_fsr_easu_16.sc @@ -0,0 +1,3 @@ +#define SAMPLE_EASU 1 + +#include "cs_fsr.h" diff --git a/examples/46-fsr/cs_fsr_easu_32.sc b/examples/46-fsr/cs_fsr_easu_32.sc new file mode 100644 index 000000000..bb6c76307 --- /dev/null +++ b/examples/46-fsr/cs_fsr_easu_32.sc @@ -0,0 +1,4 @@ +#define SAMPLE_SLOW_FALLBACK 1 +#define SAMPLE_EASU 1 + +#include "cs_fsr.h" diff --git a/examples/46-fsr/cs_fsr_rcas_16.sc b/examples/46-fsr/cs_fsr_rcas_16.sc new file mode 100644 index 000000000..c54645aaa --- /dev/null +++ b/examples/46-fsr/cs_fsr_rcas_16.sc @@ -0,0 +1,3 @@ +#define SAMPLE_RCAS 1 + +#include "cs_fsr.h" diff --git a/examples/46-fsr/cs_fsr_rcas_32.sc b/examples/46-fsr/cs_fsr_rcas_32.sc new file mode 100644 index 000000000..52d4693f7 --- /dev/null +++ b/examples/46-fsr/cs_fsr_rcas_32.sc @@ -0,0 +1,4 @@ +#define SAMPLE_SLOW_FALLBACK 1 +#define SAMPLE_RCAS 1 + +#include "cs_fsr.h" diff --git a/examples/46-fsr/ffx_a.h b/examples/46-fsr/ffx_a.h new file mode 100644 index 000000000..e54756077 --- /dev/null +++ b/examples/46-fsr/ffx_a.h @@ -0,0 +1,2637 @@ +//============================================================================================================================== +// +// [A] SHADER PORTABILITY 1.20210629 +// +//============================================================================================================================== +// FidelityFX Super Resolution Sample +// +// Copyright (c) 2021 Advanced Micro Devices, Inc. All rights reserved. +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//------------------------------------------------------------------------------------------------------------------------------ +// MIT LICENSE +// =========== +// Copyright (c) 2014 Michal Drobot (for concepts used in "FLOAT APPROXIMATIONS"). +// ----------- +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, +// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// ----------- +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +// Software. +// ----------- +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +//------------------------------------------------------------------------------------------------------------------------------ +// ABOUT +// ===== +// Common central point for high-level shading language and C portability for various shader headers. +//------------------------------------------------------------------------------------------------------------------------------ +// DEFINES +// ======= +// A_CPU ..... Include the CPU related code. +// A_GPU ..... Include the GPU related code. +// A_GLSL .... Using GLSL. +// A_HLSL .... Using HLSL. +// A_HLSL_6_2 Using HLSL 6.2 with new 'uint16_t' and related types (requires '-enable-16bit-types'). +// A_NO_16_BIT_CAST Don't use instructions that are not availabe in SPIR-V (needed for running A_HLSL_6_2 on Vulkan) +// A_GCC ..... Using a GCC compatible compiler (else assume MSVC compatible compiler by default). +// ======= +// A_BYTE .... Support 8-bit integer. +// A_HALF .... Support 16-bit integer and floating point. +// A_LONG .... Support 64-bit integer. +// A_DUBL .... Support 64-bit floating point. +// ======= +// A_WAVE .... Support wave-wide operations. +//------------------------------------------------------------------------------------------------------------------------------ +// To get #include "ffx_a.h" working in GLSL use '#extension GL_GOOGLE_include_directive:require'. +//------------------------------------------------------------------------------------------------------------------------------ +// SIMPLIFIED TYPE SYSTEM +// ====================== +// - All ints will be unsigned with exception of when signed is required. +// - Type naming simplified and shortened "A<#components>", +// - H = 16-bit float (half) +// - F = 32-bit float (float) +// - D = 64-bit float (double) +// - P = 1-bit integer (predicate, not using bool because 'B' is used for byte) +// - B = 8-bit integer (byte) +// - W = 16-bit integer (word) +// - U = 32-bit integer (unsigned) +// - L = 64-bit integer (long) +// - Using "AS<#components>" for signed when required. +//------------------------------------------------------------------------------------------------------------------------------ +// TODO +// ==== +// - Make sure 'ALerp*(a,b,m)' does 'b*m+(-a*m+a)' (2 ops). +//------------------------------------------------------------------------------------------------------------------------------ +// CHANGE LOG +// ========== +// 20200914 - Expanded wave ops and prx code. +// 20200713 - Added [ZOL] section, fixed serious bugs in sRGB and Rec.709 color conversion code, etc. +//============================================================================================================================== +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// COMMON +//============================================================================================================================== +#define A_2PI 6.28318530718 +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// CPU +// +// +//============================================================================================================================== +#ifdef A_CPU + // Supporting user defined overrides. + #ifndef A_RESTRICT + #define A_RESTRICT __restrict + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifndef A_STATIC + #define A_STATIC static + #endif +//------------------------------------------------------------------------------------------------------------------------------ + // Same types across CPU and GPU. + // Predicate uses 32-bit integer (C friendly bool). + typedef uint32_t AP1; + typedef float AF1; + typedef double AD1; + typedef uint8_t AB1; + typedef uint16_t AW1; + typedef uint32_t AU1; + typedef uint64_t AL1; + typedef int8_t ASB1; + typedef int16_t ASW1; + typedef int32_t ASU1; + typedef int64_t ASL1; +//------------------------------------------------------------------------------------------------------------------------------ + #define AD1_(a) ((AD1)(a)) + #define AF1_(a) ((AF1)(a)) + #define AL1_(a) ((AL1)(a)) + #define AU1_(a) ((AU1)(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define ASL1_(a) ((ASL1)(a)) + #define ASU1_(a) ((ASU1)(a)) +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AU1 AU1_AF1(AF1 a){union{AF1 f;AU1 u;}bits;bits.f=a;return bits.u;} +//------------------------------------------------------------------------------------------------------------------------------ + #define A_TRUE 1 + #define A_FALSE 0 +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// CPU/GPU PORTING +// +//------------------------------------------------------------------------------------------------------------------------------ +// Get CPU and GPU to share all setup code, without duplicate code paths. +// This uses a lower-case prefix for special vector constructs. +// - In C restrict pointers are used. +// - In the shading language, in/inout/out arguments are used. +// This depends on the ability to access a vector value in both languages via array syntax (aka color[2]). +//============================================================================================================================== +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// VECTOR ARGUMENT/RETURN/INITIALIZATION PORTABILITY +//============================================================================================================================== + #define retAD2 AD1 *A_RESTRICT + #define retAD3 AD1 *A_RESTRICT + #define retAD4 AD1 *A_RESTRICT + #define retAF2 AF1 *A_RESTRICT + #define retAF3 AF1 *A_RESTRICT + #define retAF4 AF1 *A_RESTRICT + #define retAL2 AL1 *A_RESTRICT + #define retAL3 AL1 *A_RESTRICT + #define retAL4 AL1 *A_RESTRICT + #define retAU2 AU1 *A_RESTRICT + #define retAU3 AU1 *A_RESTRICT + #define retAU4 AU1 *A_RESTRICT +//------------------------------------------------------------------------------------------------------------------------------ + #define inAD2 AD1 *A_RESTRICT + #define inAD3 AD1 *A_RESTRICT + #define inAD4 AD1 *A_RESTRICT + #define inAF2 AF1 *A_RESTRICT + #define inAF3 AF1 *A_RESTRICT + #define inAF4 AF1 *A_RESTRICT + #define inAL2 AL1 *A_RESTRICT + #define inAL3 AL1 *A_RESTRICT + #define inAL4 AL1 *A_RESTRICT + #define inAU2 AU1 *A_RESTRICT + #define inAU3 AU1 *A_RESTRICT + #define inAU4 AU1 *A_RESTRICT +//------------------------------------------------------------------------------------------------------------------------------ + #define inoutAD2 AD1 *A_RESTRICT + #define inoutAD3 AD1 *A_RESTRICT + #define inoutAD4 AD1 *A_RESTRICT + #define inoutAF2 AF1 *A_RESTRICT + #define inoutAF3 AF1 *A_RESTRICT + #define inoutAF4 AF1 *A_RESTRICT + #define inoutAL2 AL1 *A_RESTRICT + #define inoutAL3 AL1 *A_RESTRICT + #define inoutAL4 AL1 *A_RESTRICT + #define inoutAU2 AU1 *A_RESTRICT + #define inoutAU3 AU1 *A_RESTRICT + #define inoutAU4 AU1 *A_RESTRICT +//------------------------------------------------------------------------------------------------------------------------------ + #define outAD2 AD1 *A_RESTRICT + #define outAD3 AD1 *A_RESTRICT + #define outAD4 AD1 *A_RESTRICT + #define outAF2 AF1 *A_RESTRICT + #define outAF3 AF1 *A_RESTRICT + #define outAF4 AF1 *A_RESTRICT + #define outAL2 AL1 *A_RESTRICT + #define outAL3 AL1 *A_RESTRICT + #define outAL4 AL1 *A_RESTRICT + #define outAU2 AU1 *A_RESTRICT + #define outAU3 AU1 *A_RESTRICT + #define outAU4 AU1 *A_RESTRICT +//------------------------------------------------------------------------------------------------------------------------------ + #define varAD2(x) AD1 x[2] + #define varAD3(x) AD1 x[3] + #define varAD4(x) AD1 x[4] + #define varAF2(x) AF1 x[2] + #define varAF3(x) AF1 x[3] + #define varAF4(x) AF1 x[4] + #define varAL2(x) AL1 x[2] + #define varAL3(x) AL1 x[3] + #define varAL4(x) AL1 x[4] + #define varAU2(x) AU1 x[2] + #define varAU3(x) AU1 x[3] + #define varAU4(x) AU1 x[4] +//------------------------------------------------------------------------------------------------------------------------------ + #define initAD2(x,y) {x,y} + #define initAD3(x,y,z) {x,y,z} + #define initAD4(x,y,z,w) {x,y,z,w} + #define initAF2(x,y) {x,y} + #define initAF3(x,y,z) {x,y,z} + #define initAF4(x,y,z,w) {x,y,z,w} + #define initAL2(x,y) {x,y} + #define initAL3(x,y,z) {x,y,z} + #define initAL4(x,y,z,w) {x,y,z,w} + #define initAU2(x,y) {x,y} + #define initAU3(x,y,z) {x,y,z} + #define initAU4(x,y,z,w) {x,y,z,w} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// SCALAR RETURN OPS +//------------------------------------------------------------------------------------------------------------------------------ +// TODO +// ==== +// - Replace transcendentals with manual versions. +//============================================================================================================================== + #ifdef A_GCC + A_STATIC AD1 AAbsD1(AD1 a){return __builtin_fabs(a);} + A_STATIC AF1 AAbsF1(AF1 a){return __builtin_fabsf(a);} + A_STATIC AU1 AAbsSU1(AU1 a){return AU1_(__builtin_abs(ASU1_(a)));} + A_STATIC AL1 AAbsSL1(AL1 a){return AL1_(__builtin_llabs(ASL1_(a)));} + #else + A_STATIC AD1 AAbsD1(AD1 a){return fabs(a);} + A_STATIC AF1 AAbsF1(AF1 a){return fabsf(a);} + A_STATIC AU1 AAbsSU1(AU1 a){return AU1_(abs(ASU1_(a)));} + A_STATIC AL1 AAbsSL1(AL1 a){return AL1_(labs((long)ASL1_(a)));} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 ACosD1(AD1 a){return __builtin_cos(a);} + A_STATIC AF1 ACosF1(AF1 a){return __builtin_cosf(a);} + #else + A_STATIC AD1 ACosD1(AD1 a){return cos(a);} + A_STATIC AF1 ACosF1(AF1 a){return cosf(a);} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 ADotD2(inAD2 a,inAD2 b){return a[0]*b[0]+a[1]*b[1];} + A_STATIC AD1 ADotD3(inAD3 a,inAD3 b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2];} + A_STATIC AD1 ADotD4(inAD4 a,inAD4 b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]+a[3]*b[3];} + A_STATIC AF1 ADotF2(inAF2 a,inAF2 b){return a[0]*b[0]+a[1]*b[1];} + A_STATIC AF1 ADotF3(inAF3 a,inAF3 b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2];} + A_STATIC AF1 ADotF4(inAF4 a,inAF4 b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]+a[3]*b[3];} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 AExp2D1(AD1 a){return __builtin_exp2(a);} + A_STATIC AF1 AExp2F1(AF1 a){return __builtin_exp2f(a);} + #else + A_STATIC AD1 AExp2D1(AD1 a){return exp2(a);} + A_STATIC AF1 AExp2F1(AF1 a){return exp2f(a);} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 AFloorD1(AD1 a){return __builtin_floor(a);} + A_STATIC AF1 AFloorF1(AF1 a){return __builtin_floorf(a);} + #else + A_STATIC AD1 AFloorD1(AD1 a){return floor(a);} + A_STATIC AF1 AFloorF1(AF1 a){return floorf(a);} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 ALerpD1(AD1 a,AD1 b,AD1 c){return b*c+(-a*c+a);} + A_STATIC AF1 ALerpF1(AF1 a,AF1 b,AF1 c){return b*c+(-a*c+a);} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 ALog2D1(AD1 a){return __builtin_log2(a);} + A_STATIC AF1 ALog2F1(AF1 a){return __builtin_log2f(a);} + #else + A_STATIC AD1 ALog2D1(AD1 a){return log2(a);} + A_STATIC AF1 ALog2F1(AF1 a){return log2f(a);} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 AMaxD1(AD1 a,AD1 b){return a>b?a:b;} + A_STATIC AF1 AMaxF1(AF1 a,AF1 b){return a>b?a:b;} + A_STATIC AL1 AMaxL1(AL1 a,AL1 b){return a>b?a:b;} + A_STATIC AU1 AMaxU1(AU1 a,AU1 b){return a>b?a:b;} +//------------------------------------------------------------------------------------------------------------------------------ + // These follow the convention that A integer types don't have signage, until they are operated on. + A_STATIC AL1 AMaxSL1(AL1 a,AL1 b){return (ASL1_(a)>ASL1_(b))?a:b;} + A_STATIC AU1 AMaxSU1(AU1 a,AU1 b){return (ASU1_(a)>ASU1_(b))?a:b;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 AMinD1(AD1 a,AD1 b){return a>ASL1_(b));} + A_STATIC AU1 AShrSU1(AU1 a,AU1 b){return AU1_(ASU1_(a)>>ASU1_(b));} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 ASinD1(AD1 a){return __builtin_sin(a);} + A_STATIC AF1 ASinF1(AF1 a){return __builtin_sinf(a);} + #else + A_STATIC AD1 ASinD1(AD1 a){return sin(a);} + A_STATIC AF1 ASinF1(AF1 a){return sinf(a);} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 ASqrtD1(AD1 a){return __builtin_sqrt(a);} + A_STATIC AF1 ASqrtF1(AF1 a){return __builtin_sqrtf(a);} + #else + A_STATIC AD1 ASqrtD1(AD1 a){return sqrt(a);} + A_STATIC AF1 ASqrtF1(AF1 a){return sqrtf(a);} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// SCALAR RETURN OPS - DEPENDENT +//============================================================================================================================== + A_STATIC AD1 AClampD1(AD1 x,AD1 n,AD1 m){return AMaxD1(n,AMinD1(x,m));} + A_STATIC AF1 AClampF1(AF1 x,AF1 n,AF1 m){return AMaxF1(n,AMinF1(x,m));} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 AFractD1(AD1 a){return a-AFloorD1(a);} + A_STATIC AF1 AFractF1(AF1 a){return a-AFloorF1(a);} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 APowD1(AD1 a,AD1 b){return AExp2D1(b*ALog2D1(a));} + A_STATIC AF1 APowF1(AF1 a,AF1 b){return AExp2F1(b*ALog2F1(a));} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 ARsqD1(AD1 a){return ARcpD1(ASqrtD1(a));} + A_STATIC AF1 ARsqF1(AF1 a){return ARcpF1(ASqrtF1(a));} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 ASatD1(AD1 a){return AMinD1(1.0,AMaxD1(0.0,a));} + A_STATIC AF1 ASatF1(AF1 a){return AMinF1(1.0f,AMaxF1(0.0f,a));} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// VECTOR OPS +//------------------------------------------------------------------------------------------------------------------------------ +// These are added as needed for production or prototyping, so not necessarily a complete set. +// They follow a convention of taking in a destination and also returning the destination value to increase utility. +//============================================================================================================================== + A_STATIC retAD2 opAAbsD2(outAD2 d,inAD2 a){d[0]=AAbsD1(a[0]);d[1]=AAbsD1(a[1]);return d;} + A_STATIC retAD3 opAAbsD3(outAD3 d,inAD3 a){d[0]=AAbsD1(a[0]);d[1]=AAbsD1(a[1]);d[2]=AAbsD1(a[2]);return d;} + A_STATIC retAD4 opAAbsD4(outAD4 d,inAD4 a){d[0]=AAbsD1(a[0]);d[1]=AAbsD1(a[1]);d[2]=AAbsD1(a[2]);d[3]=AAbsD1(a[3]);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAAbsF2(outAF2 d,inAF2 a){d[0]=AAbsF1(a[0]);d[1]=AAbsF1(a[1]);return d;} + A_STATIC retAF3 opAAbsF3(outAF3 d,inAF3 a){d[0]=AAbsF1(a[0]);d[1]=AAbsF1(a[1]);d[2]=AAbsF1(a[2]);return d;} + A_STATIC retAF4 opAAbsF4(outAF4 d,inAF4 a){d[0]=AAbsF1(a[0]);d[1]=AAbsF1(a[1]);d[2]=AAbsF1(a[2]);d[3]=AAbsF1(a[3]);return d;} +//============================================================================================================================== + A_STATIC retAD2 opAAddD2(outAD2 d,inAD2 a,inAD2 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];return d;} + A_STATIC retAD3 opAAddD3(outAD3 d,inAD3 a,inAD3 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];d[2]=a[2]+b[2];return d;} + A_STATIC retAD4 opAAddD4(outAD4 d,inAD4 a,inAD4 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];d[2]=a[2]+b[2];d[3]=a[3]+b[3];return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAAddF2(outAF2 d,inAF2 a,inAF2 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];return d;} + A_STATIC retAF3 opAAddF3(outAF3 d,inAF3 a,inAF3 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];d[2]=a[2]+b[2];return d;} + A_STATIC retAF4 opAAddF4(outAF4 d,inAF4 a,inAF4 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];d[2]=a[2]+b[2];d[3]=a[3]+b[3];return d;} +//============================================================================================================================== + A_STATIC retAD2 opAAddOneD2(outAD2 d,inAD2 a,AD1 b){d[0]=a[0]+b;d[1]=a[1]+b;return d;} + A_STATIC retAD3 opAAddOneD3(outAD3 d,inAD3 a,AD1 b){d[0]=a[0]+b;d[1]=a[1]+b;d[2]=a[2]+b;return d;} + A_STATIC retAD4 opAAddOneD4(outAD4 d,inAD4 a,AD1 b){d[0]=a[0]+b;d[1]=a[1]+b;d[2]=a[2]+b;d[3]=a[3]+b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAAddOneF2(outAF2 d,inAF2 a,AF1 b){d[0]=a[0]+b;d[1]=a[1]+b;return d;} + A_STATIC retAF3 opAAddOneF3(outAF3 d,inAF3 a,AF1 b){d[0]=a[0]+b;d[1]=a[1]+b;d[2]=a[2]+b;return d;} + A_STATIC retAF4 opAAddOneF4(outAF4 d,inAF4 a,AF1 b){d[0]=a[0]+b;d[1]=a[1]+b;d[2]=a[2]+b;d[3]=a[3]+b;return d;} +//============================================================================================================================== + A_STATIC retAD2 opACpyD2(outAD2 d,inAD2 a){d[0]=a[0];d[1]=a[1];return d;} + A_STATIC retAD3 opACpyD3(outAD3 d,inAD3 a){d[0]=a[0];d[1]=a[1];d[2]=a[2];return d;} + A_STATIC retAD4 opACpyD4(outAD4 d,inAD4 a){d[0]=a[0];d[1]=a[1];d[2]=a[2];d[3]=a[3];return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opACpyF2(outAF2 d,inAF2 a){d[0]=a[0];d[1]=a[1];return d;} + A_STATIC retAF3 opACpyF3(outAF3 d,inAF3 a){d[0]=a[0];d[1]=a[1];d[2]=a[2];return d;} + A_STATIC retAF4 opACpyF4(outAF4 d,inAF4 a){d[0]=a[0];d[1]=a[1];d[2]=a[2];d[3]=a[3];return d;} +//============================================================================================================================== + A_STATIC retAD2 opALerpD2(outAD2 d,inAD2 a,inAD2 b,inAD2 c){d[0]=ALerpD1(a[0],b[0],c[0]);d[1]=ALerpD1(a[1],b[1],c[1]);return d;} + A_STATIC retAD3 opALerpD3(outAD3 d,inAD3 a,inAD3 b,inAD3 c){d[0]=ALerpD1(a[0],b[0],c[0]);d[1]=ALerpD1(a[1],b[1],c[1]);d[2]=ALerpD1(a[2],b[2],c[2]);return d;} + A_STATIC retAD4 opALerpD4(outAD4 d,inAD4 a,inAD4 b,inAD4 c){d[0]=ALerpD1(a[0],b[0],c[0]);d[1]=ALerpD1(a[1],b[1],c[1]);d[2]=ALerpD1(a[2],b[2],c[2]);d[3]=ALerpD1(a[3],b[3],c[3]);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opALerpF2(outAF2 d,inAF2 a,inAF2 b,inAF2 c){d[0]=ALerpF1(a[0],b[0],c[0]);d[1]=ALerpF1(a[1],b[1],c[1]);return d;} + A_STATIC retAF3 opALerpF3(outAF3 d,inAF3 a,inAF3 b,inAF3 c){d[0]=ALerpF1(a[0],b[0],c[0]);d[1]=ALerpF1(a[1],b[1],c[1]);d[2]=ALerpF1(a[2],b[2],c[2]);return d;} + A_STATIC retAF4 opALerpF4(outAF4 d,inAF4 a,inAF4 b,inAF4 c){d[0]=ALerpF1(a[0],b[0],c[0]);d[1]=ALerpF1(a[1],b[1],c[1]);d[2]=ALerpF1(a[2],b[2],c[2]);d[3]=ALerpF1(a[3],b[3],c[3]);return d;} +//============================================================================================================================== + A_STATIC retAD2 opALerpOneD2(outAD2 d,inAD2 a,inAD2 b,AD1 c){d[0]=ALerpD1(a[0],b[0],c);d[1]=ALerpD1(a[1],b[1],c);return d;} + A_STATIC retAD3 opALerpOneD3(outAD3 d,inAD3 a,inAD3 b,AD1 c){d[0]=ALerpD1(a[0],b[0],c);d[1]=ALerpD1(a[1],b[1],c);d[2]=ALerpD1(a[2],b[2],c);return d;} + A_STATIC retAD4 opALerpOneD4(outAD4 d,inAD4 a,inAD4 b,AD1 c){d[0]=ALerpD1(a[0],b[0],c);d[1]=ALerpD1(a[1],b[1],c);d[2]=ALerpD1(a[2],b[2],c);d[3]=ALerpD1(a[3],b[3],c);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opALerpOneF2(outAF2 d,inAF2 a,inAF2 b,AF1 c){d[0]=ALerpF1(a[0],b[0],c);d[1]=ALerpF1(a[1],b[1],c);return d;} + A_STATIC retAF3 opALerpOneF3(outAF3 d,inAF3 a,inAF3 b,AF1 c){d[0]=ALerpF1(a[0],b[0],c);d[1]=ALerpF1(a[1],b[1],c);d[2]=ALerpF1(a[2],b[2],c);return d;} + A_STATIC retAF4 opALerpOneF4(outAF4 d,inAF4 a,inAF4 b,AF1 c){d[0]=ALerpF1(a[0],b[0],c);d[1]=ALerpF1(a[1],b[1],c);d[2]=ALerpF1(a[2],b[2],c);d[3]=ALerpF1(a[3],b[3],c);return d;} +//============================================================================================================================== + A_STATIC retAD2 opAMaxD2(outAD2 d,inAD2 a,inAD2 b){d[0]=AMaxD1(a[0],b[0]);d[1]=AMaxD1(a[1],b[1]);return d;} + A_STATIC retAD3 opAMaxD3(outAD3 d,inAD3 a,inAD3 b){d[0]=AMaxD1(a[0],b[0]);d[1]=AMaxD1(a[1],b[1]);d[2]=AMaxD1(a[2],b[2]);return d;} + A_STATIC retAD4 opAMaxD4(outAD4 d,inAD4 a,inAD4 b){d[0]=AMaxD1(a[0],b[0]);d[1]=AMaxD1(a[1],b[1]);d[2]=AMaxD1(a[2],b[2]);d[3]=AMaxD1(a[3],b[3]);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAMaxF2(outAF2 d,inAF2 a,inAF2 b){d[0]=AMaxF1(a[0],b[0]);d[1]=AMaxF1(a[1],b[1]);return d;} + A_STATIC retAF3 opAMaxF3(outAF3 d,inAF3 a,inAF3 b){d[0]=AMaxF1(a[0],b[0]);d[1]=AMaxF1(a[1],b[1]);d[2]=AMaxF1(a[2],b[2]);return d;} + A_STATIC retAF4 opAMaxF4(outAF4 d,inAF4 a,inAF4 b){d[0]=AMaxF1(a[0],b[0]);d[1]=AMaxF1(a[1],b[1]);d[2]=AMaxF1(a[2],b[2]);d[3]=AMaxF1(a[3],b[3]);return d;} +//============================================================================================================================== + A_STATIC retAD2 opAMinD2(outAD2 d,inAD2 a,inAD2 b){d[0]=AMinD1(a[0],b[0]);d[1]=AMinD1(a[1],b[1]);return d;} + A_STATIC retAD3 opAMinD3(outAD3 d,inAD3 a,inAD3 b){d[0]=AMinD1(a[0],b[0]);d[1]=AMinD1(a[1],b[1]);d[2]=AMinD1(a[2],b[2]);return d;} + A_STATIC retAD4 opAMinD4(outAD4 d,inAD4 a,inAD4 b){d[0]=AMinD1(a[0],b[0]);d[1]=AMinD1(a[1],b[1]);d[2]=AMinD1(a[2],b[2]);d[3]=AMinD1(a[3],b[3]);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAMinF2(outAF2 d,inAF2 a,inAF2 b){d[0]=AMinF1(a[0],b[0]);d[1]=AMinF1(a[1],b[1]);return d;} + A_STATIC retAF3 opAMinF3(outAF3 d,inAF3 a,inAF3 b){d[0]=AMinF1(a[0],b[0]);d[1]=AMinF1(a[1],b[1]);d[2]=AMinF1(a[2],b[2]);return d;} + A_STATIC retAF4 opAMinF4(outAF4 d,inAF4 a,inAF4 b){d[0]=AMinF1(a[0],b[0]);d[1]=AMinF1(a[1],b[1]);d[2]=AMinF1(a[2],b[2]);d[3]=AMinF1(a[3],b[3]);return d;} +//============================================================================================================================== + A_STATIC retAD2 opAMulD2(outAD2 d,inAD2 a,inAD2 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];return d;} + A_STATIC retAD3 opAMulD3(outAD3 d,inAD3 a,inAD3 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];d[2]=a[2]*b[2];return d;} + A_STATIC retAD4 opAMulD4(outAD4 d,inAD4 a,inAD4 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];d[2]=a[2]*b[2];d[3]=a[3]*b[3];return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAMulF2(outAF2 d,inAF2 a,inAF2 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];return d;} + A_STATIC retAF3 opAMulF3(outAF3 d,inAF3 a,inAF3 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];d[2]=a[2]*b[2];return d;} + A_STATIC retAF4 opAMulF4(outAF4 d,inAF4 a,inAF4 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];d[2]=a[2]*b[2];d[3]=a[3]*b[3];return d;} +//============================================================================================================================== + A_STATIC retAD2 opAMulOneD2(outAD2 d,inAD2 a,AD1 b){d[0]=a[0]*b;d[1]=a[1]*b;return d;} + A_STATIC retAD3 opAMulOneD3(outAD3 d,inAD3 a,AD1 b){d[0]=a[0]*b;d[1]=a[1]*b;d[2]=a[2]*b;return d;} + A_STATIC retAD4 opAMulOneD4(outAD4 d,inAD4 a,AD1 b){d[0]=a[0]*b;d[1]=a[1]*b;d[2]=a[2]*b;d[3]=a[3]*b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAMulOneF2(outAF2 d,inAF2 a,AF1 b){d[0]=a[0]*b;d[1]=a[1]*b;return d;} + A_STATIC retAF3 opAMulOneF3(outAF3 d,inAF3 a,AF1 b){d[0]=a[0]*b;d[1]=a[1]*b;d[2]=a[2]*b;return d;} + A_STATIC retAF4 opAMulOneF4(outAF4 d,inAF4 a,AF1 b){d[0]=a[0]*b;d[1]=a[1]*b;d[2]=a[2]*b;d[3]=a[3]*b;return d;} +//============================================================================================================================== + A_STATIC retAD2 opANegD2(outAD2 d,inAD2 a){d[0]=-a[0];d[1]=-a[1];return d;} + A_STATIC retAD3 opANegD3(outAD3 d,inAD3 a){d[0]=-a[0];d[1]=-a[1];d[2]=-a[2];return d;} + A_STATIC retAD4 opANegD4(outAD4 d,inAD4 a){d[0]=-a[0];d[1]=-a[1];d[2]=-a[2];d[3]=-a[3];return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opANegF2(outAF2 d,inAF2 a){d[0]=-a[0];d[1]=-a[1];return d;} + A_STATIC retAF3 opANegF3(outAF3 d,inAF3 a){d[0]=-a[0];d[1]=-a[1];d[2]=-a[2];return d;} + A_STATIC retAF4 opANegF4(outAF4 d,inAF4 a){d[0]=-a[0];d[1]=-a[1];d[2]=-a[2];d[3]=-a[3];return d;} +//============================================================================================================================== + A_STATIC retAD2 opARcpD2(outAD2 d,inAD2 a){d[0]=ARcpD1(a[0]);d[1]=ARcpD1(a[1]);return d;} + A_STATIC retAD3 opARcpD3(outAD3 d,inAD3 a){d[0]=ARcpD1(a[0]);d[1]=ARcpD1(a[1]);d[2]=ARcpD1(a[2]);return d;} + A_STATIC retAD4 opARcpD4(outAD4 d,inAD4 a){d[0]=ARcpD1(a[0]);d[1]=ARcpD1(a[1]);d[2]=ARcpD1(a[2]);d[3]=ARcpD1(a[3]);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opARcpF2(outAF2 d,inAF2 a){d[0]=ARcpF1(a[0]);d[1]=ARcpF1(a[1]);return d;} + A_STATIC retAF3 opARcpF3(outAF3 d,inAF3 a){d[0]=ARcpF1(a[0]);d[1]=ARcpF1(a[1]);d[2]=ARcpF1(a[2]);return d;} + A_STATIC retAF4 opARcpF4(outAF4 d,inAF4 a){d[0]=ARcpF1(a[0]);d[1]=ARcpF1(a[1]);d[2]=ARcpF1(a[2]);d[3]=ARcpF1(a[3]);return d;} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// HALF FLOAT PACKING +//============================================================================================================================== + // Convert float to half (in lower 16-bits of output). + // Same fast technique as documented here: ftp://ftp.fox-toolkit.org/pub/fasthalffloatconversion.pdf + // Supports denormals. + // Conversion rules are to make computations possibly "safer" on the GPU, + // -INF & -NaN -> -65504 + // +INF & +NaN -> +65504 + A_STATIC AU1 AU1_AH1_AF1(AF1 f){ + static AW1 base[512]={ + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0001,0x0002,0x0004,0x0008,0x0010,0x0020,0x0040,0x0080,0x0100, + 0x0200,0x0400,0x0800,0x0c00,0x1000,0x1400,0x1800,0x1c00,0x2000,0x2400,0x2800,0x2c00,0x3000,0x3400,0x3800,0x3c00, + 0x4000,0x4400,0x4800,0x4c00,0x5000,0x5400,0x5800,0x5c00,0x6000,0x6400,0x6800,0x6c00,0x7000,0x7400,0x7800,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8001,0x8002,0x8004,0x8008,0x8010,0x8020,0x8040,0x8080,0x8100, + 0x8200,0x8400,0x8800,0x8c00,0x9000,0x9400,0x9800,0x9c00,0xa000,0xa400,0xa800,0xac00,0xb000,0xb400,0xb800,0xbc00, + 0xc000,0xc400,0xc800,0xcc00,0xd000,0xd400,0xd800,0xdc00,0xe000,0xe400,0xe800,0xec00,0xf000,0xf400,0xf800,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff}; + static AB1 shift[512]={ + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x17,0x16,0x15,0x14,0x13,0x12,0x11,0x10,0x0f, + 0x0e,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d, + 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x17,0x16,0x15,0x14,0x13,0x12,0x11,0x10,0x0f, + 0x0e,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d, + 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18}; + union{AF1 f;AU1 u;}bits;bits.f=f;AU1 u=bits.u;AU1 i=u>>23;return (AU1)(base[i])+((u&0x7fffff)>>shift[i]);} +//------------------------------------------------------------------------------------------------------------------------------ + // Used to output packed constant. + A_STATIC AU1 AU1_AH2_AF2(inAF2 a){return AU1_AH1_AF1(a[0])+(AU1_AH1_AF1(a[1])<<16);} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// GLSL +// +// +//============================================================================================================================== +#if defined(A_GLSL) && defined(A_GPU) + #define AP1 bool + #define AP2 bvec2 + #define AP3 bvec3 + #define AP4 bvec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AF1 float + #define AF2 vec2 + #define AF3 vec3 + #define AF4 vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1 uint + #define AU2 uvec2 + #define AU3 uvec3 + #define AU4 uvec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASU1 int + #define ASU2 ivec2 + #define ASU3 ivec3 + #define ASU4 ivec4 +//============================================================================================================================== + #define AF1_AU1(x) uintBitsToFloat(AU1(x)) + #define AF2_AU2(x) uintBitsToFloat(AU2(x)) + #define AF3_AU3(x) uintBitsToFloat(AU3(x)) + #define AF4_AU4(x) uintBitsToFloat(AU4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1_AF1(x) floatBitsToUint(AF1(x)) + #define AU2_AF2(x) floatBitsToUint(AF2(x)) + #define AU3_AF3(x) floatBitsToUint(AF3(x)) + #define AU4_AF4(x) floatBitsToUint(AF4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_AH1_AF1_x(AF1 a){return packHalf2x16(AF2(a,0.0));} + #define AU1_AH1_AF1(a) AU1_AH1_AF1_x(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1_AH2_AF2 packHalf2x16 + #define AU1_AW2Unorm_AF2 packUnorm2x16 + #define AU1_AB4Unorm_AF4 packUnorm4x8 +//------------------------------------------------------------------------------------------------------------------------------ + #define AF2_AH2_AU1 unpackHalf2x16 + #define AF2_AW2Unorm_AU1 unpackUnorm2x16 + #define AF4_AB4Unorm_AU1 unpackUnorm4x8 +//============================================================================================================================== + AF1 AF1_x(AF1 a){return AF1(a);} + AF2 AF2_x(AF1 a){return AF2(a,a);} + AF3 AF3_x(AF1 a){return AF3(a,a,a);} + AF4 AF4_x(AF1 a){return AF4(a,a,a,a);} + #define AF1_(a) AF1_x(AF1(a)) + #define AF2_(a) AF2_x(AF1(a)) + #define AF3_(a) AF3_x(AF1(a)) + #define AF4_(a) AF4_x(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_x(AU1 a){return AU1(a);} + AU2 AU2_x(AU1 a){return AU2(a,a);} + AU3 AU3_x(AU1 a){return AU3(a,a,a);} + AU4 AU4_x(AU1 a){return AU4(a,a,a,a);} + #define AU1_(a) AU1_x(AU1(a)) + #define AU2_(a) AU2_x(AU1(a)) + #define AU3_(a) AU3_x(AU1(a)) + #define AU4_(a) AU4_x(AU1(a)) +//============================================================================================================================== + AU1 AAbsSU1(AU1 a){return AU1(abs(ASU1(a)));} + AU2 AAbsSU2(AU2 a){return AU2(abs(ASU2(a)));} + AU3 AAbsSU3(AU3 a){return AU3(abs(ASU3(a)));} + AU4 AAbsSU4(AU4 a){return AU4(abs(ASU4(a)));} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 ABfe(AU1 src,AU1 off,AU1 bits){return bitfieldExtract(src,ASU1(off),ASU1(bits));} + AU1 ABfi(AU1 src,AU1 ins,AU1 mask){return (ins&mask)|(src&(~mask));} + // Proxy for V_BFI_B32 where the 'mask' is set as 'bits', 'mask=(1<>ASU1(b));} + AU2 AShrSU2(AU2 a,AU2 b){return AU2(ASU2(a)>>ASU2(b));} + AU3 AShrSU3(AU3 a,AU3 b){return AU3(ASU3(a)>>ASU3(b));} + AU4 AShrSU4(AU4 a,AU4 b){return AU4(ASU4(a)>>ASU4(b));} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// GLSL BYTE +//============================================================================================================================== + #ifdef A_BYTE + #define AB1 uint8_t + #define AB2 u8vec2 + #define AB3 u8vec3 + #define AB4 u8vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASB1 int8_t + #define ASB2 i8vec2 + #define ASB3 i8vec3 + #define ASB4 i8vec4 +//------------------------------------------------------------------------------------------------------------------------------ + AB1 AB1_x(AB1 a){return AB1(a);} + AB2 AB2_x(AB1 a){return AB2(a,a);} + AB3 AB3_x(AB1 a){return AB3(a,a,a);} + AB4 AB4_x(AB1 a){return AB4(a,a,a,a);} + #define AB1_(a) AB1_x(AB1(a)) + #define AB2_(a) AB2_x(AB1(a)) + #define AB3_(a) AB3_x(AB1(a)) + #define AB4_(a) AB4_x(AB1(a)) + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// GLSL HALF +//============================================================================================================================== + #ifdef A_HALF + #define AH1 float16_t + #define AH2 f16vec2 + #define AH3 f16vec3 + #define AH4 f16vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AW1 uint16_t + #define AW2 u16vec2 + #define AW3 u16vec3 + #define AW4 u16vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASW1 int16_t + #define ASW2 i16vec2 + #define ASW3 i16vec3 + #define ASW4 i16vec4 +//============================================================================================================================== + #define AH2_AU1(x) unpackFloat2x16(AU1(x)) + AH4 AH4_AU2_x(AU2 x){return AH4(unpackFloat2x16(x.x),unpackFloat2x16(x.y));} + #define AH4_AU2(x) AH4_AU2_x(AU2(x)) + #define AW2_AU1(x) unpackUint2x16(AU1(x)) + #define AW4_AU2(x) unpackUint4x16(pack64(AU2(x))) +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1_AH2(x) packFloat2x16(AH2(x)) + AU2 AU2_AH4_x(AH4 x){return AU2(packFloat2x16(x.xy),packFloat2x16(x.zw));} + #define AU2_AH4(x) AU2_AH4_x(AH4(x)) + #define AU1_AW2(x) packUint2x16(AW2(x)) + #define AU2_AW4(x) unpack32(packUint4x16(AW4(x))) +//============================================================================================================================== + #define AW1_AH1(x) halfBitsToUint16(AH1(x)) + #define AW2_AH2(x) halfBitsToUint16(AH2(x)) + #define AW3_AH3(x) halfBitsToUint16(AH3(x)) + #define AW4_AH4(x) halfBitsToUint16(AH4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AH1_AW1(x) uint16BitsToHalf(AW1(x)) + #define AH2_AW2(x) uint16BitsToHalf(AW2(x)) + #define AH3_AW3(x) uint16BitsToHalf(AW3(x)) + #define AH4_AW4(x) uint16BitsToHalf(AW4(x)) +//============================================================================================================================== + AH1 AH1_x(AH1 a){return AH1(a);} + AH2 AH2_x(AH1 a){return AH2(a,a);} + AH3 AH3_x(AH1 a){return AH3(a,a,a);} + AH4 AH4_x(AH1 a){return AH4(a,a,a,a);} + #define AH1_(a) AH1_x(AH1(a)) + #define AH2_(a) AH2_x(AH1(a)) + #define AH3_(a) AH3_x(AH1(a)) + #define AH4_(a) AH4_x(AH1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AW1_x(AW1 a){return AW1(a);} + AW2 AW2_x(AW1 a){return AW2(a,a);} + AW3 AW3_x(AW1 a){return AW3(a,a,a);} + AW4 AW4_x(AW1 a){return AW4(a,a,a,a);} + #define AW1_(a) AW1_x(AW1(a)) + #define AW2_(a) AW2_x(AW1(a)) + #define AW3_(a) AW3_x(AW1(a)) + #define AW4_(a) AW4_x(AW1(a)) +//============================================================================================================================== + AW1 AAbsSW1(AW1 a){return AW1(abs(ASW1(a)));} + AW2 AAbsSW2(AW2 a){return AW2(abs(ASW2(a)));} + AW3 AAbsSW3(AW3 a){return AW3(abs(ASW3(a)));} + AW4 AAbsSW4(AW4 a){return AW4(abs(ASW4(a)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AClampH1(AH1 x,AH1 n,AH1 m){return clamp(x,n,m);} + AH2 AClampH2(AH2 x,AH2 n,AH2 m){return clamp(x,n,m);} + AH3 AClampH3(AH3 x,AH3 n,AH3 m){return clamp(x,n,m);} + AH4 AClampH4(AH4 x,AH4 n,AH4 m){return clamp(x,n,m);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AFractH1(AH1 x){return fract(x);} + AH2 AFractH2(AH2 x){return fract(x);} + AH3 AFractH3(AH3 x){return fract(x);} + AH4 AFractH4(AH4 x){return fract(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ALerpH1(AH1 x,AH1 y,AH1 a){return mix(x,y,a);} + AH2 ALerpH2(AH2 x,AH2 y,AH2 a){return mix(x,y,a);} + AH3 ALerpH3(AH3 x,AH3 y,AH3 a){return mix(x,y,a);} + AH4 ALerpH4(AH4 x,AH4 y,AH4 a){return mix(x,y,a);} +//------------------------------------------------------------------------------------------------------------------------------ + // No packed version of max3. + AH1 AMax3H1(AH1 x,AH1 y,AH1 z){return max(x,max(y,z));} + AH2 AMax3H2(AH2 x,AH2 y,AH2 z){return max(x,max(y,z));} + AH3 AMax3H3(AH3 x,AH3 y,AH3 z){return max(x,max(y,z));} + AH4 AMax3H4(AH4 x,AH4 y,AH4 z){return max(x,max(y,z));} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AMaxSW1(AW1 a,AW1 b){return AW1(max(ASU1(a),ASU1(b)));} + AW2 AMaxSW2(AW2 a,AW2 b){return AW2(max(ASU2(a),ASU2(b)));} + AW3 AMaxSW3(AW3 a,AW3 b){return AW3(max(ASU3(a),ASU3(b)));} + AW4 AMaxSW4(AW4 a,AW4 b){return AW4(max(ASU4(a),ASU4(b)));} +//------------------------------------------------------------------------------------------------------------------------------ + // No packed version of min3. + AH1 AMin3H1(AH1 x,AH1 y,AH1 z){return min(x,min(y,z));} + AH2 AMin3H2(AH2 x,AH2 y,AH2 z){return min(x,min(y,z));} + AH3 AMin3H3(AH3 x,AH3 y,AH3 z){return min(x,min(y,z));} + AH4 AMin3H4(AH4 x,AH4 y,AH4 z){return min(x,min(y,z));} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AMinSW1(AW1 a,AW1 b){return AW1(min(ASU1(a),ASU1(b)));} + AW2 AMinSW2(AW2 a,AW2 b){return AW2(min(ASU2(a),ASU2(b)));} + AW3 AMinSW3(AW3 a,AW3 b){return AW3(min(ASU3(a),ASU3(b)));} + AW4 AMinSW4(AW4 a,AW4 b){return AW4(min(ASU4(a),ASU4(b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ARcpH1(AH1 x){return AH1_(1.0)/x;} + AH2 ARcpH2(AH2 x){return AH2_(1.0)/x;} + AH3 ARcpH3(AH3 x){return AH3_(1.0)/x;} + AH4 ARcpH4(AH4 x){return AH4_(1.0)/x;} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ARsqH1(AH1 x){return AH1_(1.0)/sqrt(x);} + AH2 ARsqH2(AH2 x){return AH2_(1.0)/sqrt(x);} + AH3 ARsqH3(AH3 x){return AH3_(1.0)/sqrt(x);} + AH4 ARsqH4(AH4 x){return AH4_(1.0)/sqrt(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ASatH1(AH1 x){return clamp(x,AH1_(0.0),AH1_(1.0));} + AH2 ASatH2(AH2 x){return clamp(x,AH2_(0.0),AH2_(1.0));} + AH3 ASatH3(AH3 x){return clamp(x,AH3_(0.0),AH3_(1.0));} + AH4 ASatH4(AH4 x){return clamp(x,AH4_(0.0),AH4_(1.0));} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AShrSW1(AW1 a,AW1 b){return AW1(ASW1(a)>>ASW1(b));} + AW2 AShrSW2(AW2 a,AW2 b){return AW2(ASW2(a)>>ASW2(b));} + AW3 AShrSW3(AW3 a,AW3 b){return AW3(ASW3(a)>>ASW3(b));} + AW4 AShrSW4(AW4 a,AW4 b){return AW4(ASW4(a)>>ASW4(b));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// GLSL DOUBLE +//============================================================================================================================== + #ifdef A_DUBL + #define AD1 double + #define AD2 dvec2 + #define AD3 dvec3 + #define AD4 dvec4 +//------------------------------------------------------------------------------------------------------------------------------ + AD1 AD1_x(AD1 a){return AD1(a);} + AD2 AD2_x(AD1 a){return AD2(a,a);} + AD3 AD3_x(AD1 a){return AD3(a,a,a);} + AD4 AD4_x(AD1 a){return AD4(a,a,a,a);} + #define AD1_(a) AD1_x(AD1(a)) + #define AD2_(a) AD2_x(AD1(a)) + #define AD3_(a) AD3_x(AD1(a)) + #define AD4_(a) AD4_x(AD1(a)) +//============================================================================================================================== + AD1 AFractD1(AD1 x){return fract(x);} + AD2 AFractD2(AD2 x){return fract(x);} + AD3 AFractD3(AD3 x){return fract(x);} + AD4 AFractD4(AD4 x){return fract(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ALerpD1(AD1 x,AD1 y,AD1 a){return mix(x,y,a);} + AD2 ALerpD2(AD2 x,AD2 y,AD2 a){return mix(x,y,a);} + AD3 ALerpD3(AD3 x,AD3 y,AD3 a){return mix(x,y,a);} + AD4 ALerpD4(AD4 x,AD4 y,AD4 a){return mix(x,y,a);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ARcpD1(AD1 x){return AD1_(1.0)/x;} + AD2 ARcpD2(AD2 x){return AD2_(1.0)/x;} + AD3 ARcpD3(AD3 x){return AD3_(1.0)/x;} + AD4 ARcpD4(AD4 x){return AD4_(1.0)/x;} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ARsqD1(AD1 x){return AD1_(1.0)/sqrt(x);} + AD2 ARsqD2(AD2 x){return AD2_(1.0)/sqrt(x);} + AD3 ARsqD3(AD3 x){return AD3_(1.0)/sqrt(x);} + AD4 ARsqD4(AD4 x){return AD4_(1.0)/sqrt(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ASatD1(AD1 x){return clamp(x,AD1_(0.0),AD1_(1.0));} + AD2 ASatD2(AD2 x){return clamp(x,AD2_(0.0),AD2_(1.0));} + AD3 ASatD3(AD3 x){return clamp(x,AD3_(0.0),AD3_(1.0));} + AD4 ASatD4(AD4 x){return clamp(x,AD4_(0.0),AD4_(1.0));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// GLSL LONG +//============================================================================================================================== + #ifdef A_LONG + #define AL1 uint64_t + #define AL2 u64vec2 + #define AL3 u64vec3 + #define AL4 u64vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASL1 int64_t + #define ASL2 i64vec2 + #define ASL3 i64vec3 + #define ASL4 i64vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AL1_AU2(x) packUint2x32(AU2(x)) + #define AU2_AL1(x) unpackUint2x32(AL1(x)) +//------------------------------------------------------------------------------------------------------------------------------ + AL1 AL1_x(AL1 a){return AL1(a);} + AL2 AL2_x(AL1 a){return AL2(a,a);} + AL3 AL3_x(AL1 a){return AL3(a,a,a);} + AL4 AL4_x(AL1 a){return AL4(a,a,a,a);} + #define AL1_(a) AL1_x(AL1(a)) + #define AL2_(a) AL2_x(AL1(a)) + #define AL3_(a) AL3_x(AL1(a)) + #define AL4_(a) AL4_x(AL1(a)) +//============================================================================================================================== + AL1 AAbsSL1(AL1 a){return AL1(abs(ASL1(a)));} + AL2 AAbsSL2(AL2 a){return AL2(abs(ASL2(a)));} + AL3 AAbsSL3(AL3 a){return AL3(abs(ASL3(a)));} + AL4 AAbsSL4(AL4 a){return AL4(abs(ASL4(a)));} +//------------------------------------------------------------------------------------------------------------------------------ + AL1 AMaxSL1(AL1 a,AL1 b){return AL1(max(ASU1(a),ASU1(b)));} + AL2 AMaxSL2(AL2 a,AL2 b){return AL2(max(ASU2(a),ASU2(b)));} + AL3 AMaxSL3(AL3 a,AL3 b){return AL3(max(ASU3(a),ASU3(b)));} + AL4 AMaxSL4(AL4 a,AL4 b){return AL4(max(ASU4(a),ASU4(b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AL1 AMinSL1(AL1 a,AL1 b){return AL1(min(ASU1(a),ASU1(b)));} + AL2 AMinSL2(AL2 a,AL2 b){return AL2(min(ASU2(a),ASU2(b)));} + AL3 AMinSL3(AL3 a,AL3 b){return AL3(min(ASU3(a),ASU3(b)));} + AL4 AMinSL4(AL4 a,AL4 b){return AL4(min(ASU4(a),ASU4(b)));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// WAVE OPERATIONS +//============================================================================================================================== + #ifdef A_WAVE + // Where 'x' must be a compile time literal. + AF1 AWaveXorF1(AF1 v,AU1 x){return subgroupShuffleXor(v,x);} + AF2 AWaveXorF2(AF2 v,AU1 x){return subgroupShuffleXor(v,x);} + AF3 AWaveXorF3(AF3 v,AU1 x){return subgroupShuffleXor(v,x);} + AF4 AWaveXorF4(AF4 v,AU1 x){return subgroupShuffleXor(v,x);} + AU1 AWaveXorU1(AU1 v,AU1 x){return subgroupShuffleXor(v,x);} + AU2 AWaveXorU2(AU2 v,AU1 x){return subgroupShuffleXor(v,x);} + AU3 AWaveXorU3(AU3 v,AU1 x){return subgroupShuffleXor(v,x);} + AU4 AWaveXorU4(AU4 v,AU1 x){return subgroupShuffleXor(v,x);} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_HALF + AH2 AWaveXorH2(AH2 v,AU1 x){return AH2_AU1(subgroupShuffleXor(AU1_AH2(v),x));} + AH4 AWaveXorH4(AH4 v,AU1 x){return AH4_AU2(subgroupShuffleXor(AU2_AH4(v),x));} + AW2 AWaveXorW2(AW2 v,AU1 x){return AW2_AU1(subgroupShuffleXor(AU1_AW2(v),x));} + AW4 AWaveXorW4(AW4 v,AU1 x){return AW4_AU2(subgroupShuffleXor(AU2_AW4(v),x));} + #endif + #endif +//============================================================================================================================== +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// HLSL +// +// +//============================================================================================================================== +#if defined(A_HLSL) && defined(A_GPU) + #ifdef A_HLSL_6_2 + #define AP1 bool + #define AP2 bool2 + #define AP3 bool3 + #define AP4 bool4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AF1 float32_t + #define AF2 float32_t2 + #define AF3 float32_t3 + #define AF4 float32_t4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1 uint32_t + #define AU2 uint32_t2 + #define AU3 uint32_t3 + #define AU4 uint32_t4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASU1 int32_t + #define ASU2 int32_t2 + #define ASU3 int32_t3 + #define ASU4 int32_t4 + #else + #define AP1 bool + #define AP2 bool2 + #define AP3 bool3 + #define AP4 bool4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AF1 float + #define AF2 float2 + #define AF3 float3 + #define AF4 float4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1 uint + #define AU2 uint2 + #define AU3 uint3 + #define AU4 uint4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASU1 int + #define ASU2 int2 + #define ASU3 int3 + #define ASU4 int4 + #endif +//============================================================================================================================== + #define AF1_AU1(x) asfloat(AU1(x)) + #define AF2_AU2(x) asfloat(AU2(x)) + #define AF3_AU3(x) asfloat(AU3(x)) + #define AF4_AU4(x) asfloat(AU4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1_AF1(x) asuint(AF1(x)) + #define AU2_AF2(x) asuint(AF2(x)) + #define AU3_AF3(x) asuint(AF3(x)) + #define AU4_AF4(x) asuint(AF4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_AH1_AF1_x(AF1 a){return f32tof16(a);} + #define AU1_AH1_AF1(a) AU1_AH1_AF1_x(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_AH2_AF2_x(AF2 a){return f32tof16(a.x)|(f32tof16(a.y)<<16);} + #define AU1_AH2_AF2(a) AU1_AH2_AF2_x(AF2(a)) + #define AU1_AB4Unorm_AF4(x) D3DCOLORtoUBYTE4(AF4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + AF2 AF2_AH2_AU1_x(AU1 x){return AF2(f16tof32(x&0xFFFF),f16tof32(x>>16));} + #define AF2_AH2_AU1(x) AF2_AH2_AU1_x(AU1(x)) +//============================================================================================================================== + AF1 AF1_x(AF1 a){return AF1(a);} + AF2 AF2_x(AF1 a){return AF2(a,a);} + AF3 AF3_x(AF1 a){return AF3(a,a,a);} + AF4 AF4_x(AF1 a){return AF4(a,a,a,a);} + #define AF1_(a) AF1_x(AF1(a)) + #define AF2_(a) AF2_x(AF1(a)) + #define AF3_(a) AF3_x(AF1(a)) + #define AF4_(a) AF4_x(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_x(AU1 a){return AU1(a);} + AU2 AU2_x(AU1 a){return AU2(a,a);} + AU3 AU3_x(AU1 a){return AU3(a,a,a);} + AU4 AU4_x(AU1 a){return AU4(a,a,a,a);} + #define AU1_(a) AU1_x(AU1(a)) + #define AU2_(a) AU2_x(AU1(a)) + #define AU3_(a) AU3_x(AU1(a)) + #define AU4_(a) AU4_x(AU1(a)) +//============================================================================================================================== + AU1 AAbsSU1(AU1 a){return AU1(abs(ASU1(a)));} + AU2 AAbsSU2(AU2 a){return AU2(abs(ASU2(a)));} + AU3 AAbsSU3(AU3 a){return AU3(abs(ASU3(a)));} + AU4 AAbsSU4(AU4 a){return AU4(abs(ASU4(a)));} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 ABfe(AU1 src,AU1 off,AU1 bits){AU1 mask=(1u<>off)&mask;} + AU1 ABfi(AU1 src,AU1 ins,AU1 mask){return (ins&mask)|(src&(~mask));} + AU1 ABfiM(AU1 src,AU1 ins,AU1 bits){AU1 mask=(1u<>ASU1(b));} + AU2 AShrSU2(AU2 a,AU2 b){return AU2(ASU2(a)>>ASU2(b));} + AU3 AShrSU3(AU3 a,AU3 b){return AU3(ASU3(a)>>ASU3(b));} + AU4 AShrSU4(AU4 a,AU4 b){return AU4(ASU4(a)>>ASU4(b));} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// HLSL BYTE +//============================================================================================================================== + #ifdef A_BYTE + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// HLSL HALF +//============================================================================================================================== + #ifdef A_HALF + #ifdef A_HLSL_6_2 + #define AH1 float16_t + #define AH2 float16_t2 + #define AH3 float16_t3 + #define AH4 float16_t4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AW1 uint16_t + #define AW2 uint16_t2 + #define AW3 uint16_t3 + #define AW4 uint16_t4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASW1 int16_t + #define ASW2 int16_t2 + #define ASW3 int16_t3 + #define ASW4 int16_t4 + #else + #define AH1 min16float + #define AH2 min16float2 + #define AH3 min16float3 + #define AH4 min16float4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AW1 min16uint + #define AW2 min16uint2 + #define AW3 min16uint3 + #define AW4 min16uint4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASW1 min16int + #define ASW2 min16int2 + #define ASW3 min16int3 + #define ASW4 min16int4 + #endif +//============================================================================================================================== + // Need to use manual unpack to get optimal execution (don't use packed types in buffers directly). + // Unpack requires this pattern: https://gpuopen.com/first-steps-implementing-fp16/ + AH2 AH2_AU1_x(AU1 x){AF2 t=f16tof32(AU2(x&0xFFFF,x>>16));return AH2(t);} + AH4 AH4_AU2_x(AU2 x){return AH4(AH2_AU1_x(x.x),AH2_AU1_x(x.y));} + AW2 AW2_AU1_x(AU1 x){AU2 t=AU2(x&0xFFFF,x>>16);return AW2(t);} + AW4 AW4_AU2_x(AU2 x){return AW4(AW2_AU1_x(x.x),AW2_AU1_x(x.y));} + #define AH2_AU1(x) AH2_AU1_x(AU1(x)) + #define AH4_AU2(x) AH4_AU2_x(AU2(x)) + #define AW2_AU1(x) AW2_AU1_x(AU1(x)) + #define AW4_AU2(x) AW4_AU2_x(AU2(x)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_AH2_x(AH2 x){return f32tof16(x.x)+(f32tof16(x.y)<<16);} + AU2 AU2_AH4_x(AH4 x){return AU2(AU1_AH2_x(x.xy),AU1_AH2_x(x.zw));} + AU1 AU1_AW2_x(AW2 x){return AU1(x.x)+(AU1(x.y)<<16);} + AU2 AU2_AW4_x(AW4 x){return AU2(AU1_AW2_x(x.xy),AU1_AW2_x(x.zw));} + #define AU1_AH2(x) AU1_AH2_x(AH2(x)) + #define AU2_AH4(x) AU2_AH4_x(AH4(x)) + #define AU1_AW2(x) AU1_AW2_x(AW2(x)) + #define AU2_AW4(x) AU2_AW4_x(AW4(x)) +//============================================================================================================================== + #if defined(A_HLSL_6_2) && !defined(A_NO_16_BIT_CAST) + #define AW1_AH1(x) asuint16(x) + #define AW2_AH2(x) asuint16(x) + #define AW3_AH3(x) asuint16(x) + #define AW4_AH4(x) asuint16(x) + #else + #define AW1_AH1(a) AW1(f32tof16(AF1(a))) + #define AW2_AH2(a) AW2(AW1_AH1((a).x),AW1_AH1((a).y)) + #define AW3_AH3(a) AW3(AW1_AH1((a).x),AW1_AH1((a).y),AW1_AH1((a).z)) + #define AW4_AH4(a) AW4(AW1_AH1((a).x),AW1_AH1((a).y),AW1_AH1((a).z),AW1_AH1((a).w)) + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #if defined(A_HLSL_6_2) && !defined(A_NO_16_BIT_CAST) + #define AH1_AW1(x) asfloat16(x) + #define AH2_AW2(x) asfloat16(x) + #define AH3_AW3(x) asfloat16(x) + #define AH4_AW4(x) asfloat16(x) + #else + #define AH1_AW1(a) AH1(f16tof32(AU1(a))) + #define AH2_AW2(a) AH2(AH1_AW1((a).x),AH1_AW1((a).y)) + #define AH3_AW3(a) AH3(AH1_AW1((a).x),AH1_AW1((a).y),AH1_AW1((a).z)) + #define AH4_AW4(a) AH4(AH1_AW1((a).x),AH1_AW1((a).y),AH1_AW1((a).z),AH1_AW1((a).w)) + #endif +//============================================================================================================================== + AH1 AH1_x(AH1 a){return AH1(a);} + AH2 AH2_x(AH1 a){return AH2(a,a);} + AH3 AH3_x(AH1 a){return AH3(a,a,a);} + AH4 AH4_x(AH1 a){return AH4(a,a,a,a);} + #define AH1_(a) AH1_x(AH1(a)) + #define AH2_(a) AH2_x(AH1(a)) + #define AH3_(a) AH3_x(AH1(a)) + #define AH4_(a) AH4_x(AH1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AW1_x(AW1 a){return AW1(a);} + AW2 AW2_x(AW1 a){return AW2(a,a);} + AW3 AW3_x(AW1 a){return AW3(a,a,a);} + AW4 AW4_x(AW1 a){return AW4(a,a,a,a);} + #define AW1_(a) AW1_x(AW1(a)) + #define AW2_(a) AW2_x(AW1(a)) + #define AW3_(a) AW3_x(AW1(a)) + #define AW4_(a) AW4_x(AW1(a)) +//============================================================================================================================== + AW1 AAbsSW1(AW1 a){return AW1(abs(ASW1(a)));} + AW2 AAbsSW2(AW2 a){return AW2(abs(ASW2(a)));} + AW3 AAbsSW3(AW3 a){return AW3(abs(ASW3(a)));} + AW4 AAbsSW4(AW4 a){return AW4(abs(ASW4(a)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AClampH1(AH1 x,AH1 n,AH1 m){return max(n,min(x,m));} + AH2 AClampH2(AH2 x,AH2 n,AH2 m){return max(n,min(x,m));} + AH3 AClampH3(AH3 x,AH3 n,AH3 m){return max(n,min(x,m));} + AH4 AClampH4(AH4 x,AH4 n,AH4 m){return max(n,min(x,m));} +//------------------------------------------------------------------------------------------------------------------------------ + // V_FRACT_F16 (note DX frac() is different). + AH1 AFractH1(AH1 x){return x-floor(x);} + AH2 AFractH2(AH2 x){return x-floor(x);} + AH3 AFractH3(AH3 x){return x-floor(x);} + AH4 AFractH4(AH4 x){return x-floor(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ALerpH1(AH1 x,AH1 y,AH1 a){return lerp(x,y,a);} + AH2 ALerpH2(AH2 x,AH2 y,AH2 a){return lerp(x,y,a);} + AH3 ALerpH3(AH3 x,AH3 y,AH3 a){return lerp(x,y,a);} + AH4 ALerpH4(AH4 x,AH4 y,AH4 a){return lerp(x,y,a);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AMax3H1(AH1 x,AH1 y,AH1 z){return max(x,max(y,z));} + AH2 AMax3H2(AH2 x,AH2 y,AH2 z){return max(x,max(y,z));} + AH3 AMax3H3(AH3 x,AH3 y,AH3 z){return max(x,max(y,z));} + AH4 AMax3H4(AH4 x,AH4 y,AH4 z){return max(x,max(y,z));} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AMaxSW1(AW1 a,AW1 b){return AW1(max(ASU1(a),ASU1(b)));} + AW2 AMaxSW2(AW2 a,AW2 b){return AW2(max(ASU2(a),ASU2(b)));} + AW3 AMaxSW3(AW3 a,AW3 b){return AW3(max(ASU3(a),ASU3(b)));} + AW4 AMaxSW4(AW4 a,AW4 b){return AW4(max(ASU4(a),ASU4(b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AMin3H1(AH1 x,AH1 y,AH1 z){return min(x,min(y,z));} + AH2 AMin3H2(AH2 x,AH2 y,AH2 z){return min(x,min(y,z));} + AH3 AMin3H3(AH3 x,AH3 y,AH3 z){return min(x,min(y,z));} + AH4 AMin3H4(AH4 x,AH4 y,AH4 z){return min(x,min(y,z));} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AMinSW1(AW1 a,AW1 b){return AW1(min(ASU1(a),ASU1(b)));} + AW2 AMinSW2(AW2 a,AW2 b){return AW2(min(ASU2(a),ASU2(b)));} + AW3 AMinSW3(AW3 a,AW3 b){return AW3(min(ASU3(a),ASU3(b)));} + AW4 AMinSW4(AW4 a,AW4 b){return AW4(min(ASU4(a),ASU4(b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ARcpH1(AH1 x){return rcp(x);} + AH2 ARcpH2(AH2 x){return rcp(x);} + AH3 ARcpH3(AH3 x){return rcp(x);} + AH4 ARcpH4(AH4 x){return rcp(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ARsqH1(AH1 x){return rsqrt(x);} + AH2 ARsqH2(AH2 x){return rsqrt(x);} + AH3 ARsqH3(AH3 x){return rsqrt(x);} + AH4 ARsqH4(AH4 x){return rsqrt(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ASatH1(AH1 x){return saturate(x);} + AH2 ASatH2(AH2 x){return saturate(x);} + AH3 ASatH3(AH3 x){return saturate(x);} + AH4 ASatH4(AH4 x){return saturate(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AShrSW1(AW1 a,AW1 b){return AW1(ASW1(a)>>ASW1(b));} + AW2 AShrSW2(AW2 a,AW2 b){return AW2(ASW2(a)>>ASW2(b));} + AW3 AShrSW3(AW3 a,AW3 b){return AW3(ASW3(a)>>ASW3(b));} + AW4 AShrSW4(AW4 a,AW4 b){return AW4(ASW4(a)>>ASW4(b));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// HLSL DOUBLE +//============================================================================================================================== + #ifdef A_DUBL + #ifdef A_HLSL_6_2 + #define AD1 float64_t + #define AD2 float64_t2 + #define AD3 float64_t3 + #define AD4 float64_t4 + #else + #define AD1 double + #define AD2 double2 + #define AD3 double3 + #define AD4 double4 + #endif +//------------------------------------------------------------------------------------------------------------------------------ + AD1 AD1_x(AD1 a){return AD1(a);} + AD2 AD2_x(AD1 a){return AD2(a,a);} + AD3 AD3_x(AD1 a){return AD3(a,a,a);} + AD4 AD4_x(AD1 a){return AD4(a,a,a,a);} + #define AD1_(a) AD1_x(AD1(a)) + #define AD2_(a) AD2_x(AD1(a)) + #define AD3_(a) AD3_x(AD1(a)) + #define AD4_(a) AD4_x(AD1(a)) +//============================================================================================================================== + AD1 AFractD1(AD1 a){return a-floor(a);} + AD2 AFractD2(AD2 a){return a-floor(a);} + AD3 AFractD3(AD3 a){return a-floor(a);} + AD4 AFractD4(AD4 a){return a-floor(a);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ALerpD1(AD1 x,AD1 y,AD1 a){return lerp(x,y,a);} + AD2 ALerpD2(AD2 x,AD2 y,AD2 a){return lerp(x,y,a);} + AD3 ALerpD3(AD3 x,AD3 y,AD3 a){return lerp(x,y,a);} + AD4 ALerpD4(AD4 x,AD4 y,AD4 a){return lerp(x,y,a);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ARcpD1(AD1 x){return rcp(x);} + AD2 ARcpD2(AD2 x){return rcp(x);} + AD3 ARcpD3(AD3 x){return rcp(x);} + AD4 ARcpD4(AD4 x){return rcp(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ARsqD1(AD1 x){return rsqrt(x);} + AD2 ARsqD2(AD2 x){return rsqrt(x);} + AD3 ARsqD3(AD3 x){return rsqrt(x);} + AD4 ARsqD4(AD4 x){return rsqrt(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ASatD1(AD1 x){return saturate(x);} + AD2 ASatD2(AD2 x){return saturate(x);} + AD3 ASatD3(AD3 x){return saturate(x);} + AD4 ASatD4(AD4 x){return saturate(x);} + #endif +//============================================================================================================================== +// HLSL WAVE +//============================================================================================================================== + #ifdef A_WAVE + // Where 'x' must be a compile time literal. + AF1 AWaveXorF1(AF1 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AF2 AWaveXorF2(AF2 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AF3 AWaveXorF3(AF3 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AF4 AWaveXorF4(AF4 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AU1 AWaveXorU1(AU1 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AU2 AWaveXorU1(AU2 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AU3 AWaveXorU1(AU3 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AU4 AWaveXorU1(AU4 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_HALF + AH2 AWaveXorH2(AH2 v,AU1 x){return AH2_AU1(WaveReadLaneAt(AU1_AH2(v),WaveGetLaneIndex()^x));} + AH4 AWaveXorH4(AH4 v,AU1 x){return AH4_AU2(WaveReadLaneAt(AU2_AH4(v),WaveGetLaneIndex()^x));} + AW2 AWaveXorW2(AW2 v,AU1 x){return AW2_AU1(WaveReadLaneAt(AU1_AW2(v),WaveGetLaneIndex()^x));} + AW4 AWaveXorW4(AW4 v,AU1 x){return AW4_AU1(WaveReadLaneAt(AU1_AW4(v),WaveGetLaneIndex()^x));} + #endif + #endif +//============================================================================================================================== +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// GPU COMMON +// +// +//============================================================================================================================== +#ifdef A_GPU + // Negative and positive infinity. + #define A_INFP_F AF1_AU1(0x7f800000u) + #define A_INFN_F AF1_AU1(0xff800000u) +//------------------------------------------------------------------------------------------------------------------------------ + // Copy sign from 's' to positive 'd'. + AF1 ACpySgnF1(AF1 d,AF1 s){return AF1_AU1(AU1_AF1(d)|(AU1_AF1(s)&AU1_(0x80000000u)));} + AF2 ACpySgnF2(AF2 d,AF2 s){return AF2_AU2(AU2_AF2(d)|(AU2_AF2(s)&AU2_(0x80000000u)));} + AF3 ACpySgnF3(AF3 d,AF3 s){return AF3_AU3(AU3_AF3(d)|(AU3_AF3(s)&AU3_(0x80000000u)));} + AF4 ACpySgnF4(AF4 d,AF4 s){return AF4_AU4(AU4_AF4(d)|(AU4_AF4(s)&AU4_(0x80000000u)));} +//------------------------------------------------------------------------------------------------------------------------------ + // Single operation to return (useful to create a mask to use in lerp for branch free logic), + // m=NaN := 0 + // m>=0 := 0 + // m<0 := 1 + // Uses the following useful floating point logic, + // saturate(+a*(-INF)==-INF) := 0 + // saturate( 0*(-INF)== NaN) := 0 + // saturate(-a*(-INF)==+INF) := 1 + AF1 ASignedF1(AF1 m){return ASatF1(m*AF1_(A_INFN_F));} + AF2 ASignedF2(AF2 m){return ASatF2(m*AF2_(A_INFN_F));} + AF3 ASignedF3(AF3 m){return ASatF3(m*AF3_(A_INFN_F));} + AF4 ASignedF4(AF4 m){return ASatF4(m*AF4_(A_INFN_F));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AGtZeroF1(AF1 m){return ASatF1(m*AF1_(A_INFP_F));} + AF2 AGtZeroF2(AF2 m){return ASatF2(m*AF2_(A_INFP_F));} + AF3 AGtZeroF3(AF3 m){return ASatF3(m*AF3_(A_INFP_F));} + AF4 AGtZeroF4(AF4 m){return ASatF4(m*AF4_(A_INFP_F));} +//============================================================================================================================== + #ifdef A_HALF + #ifdef A_HLSL_6_2 + #define A_INFP_H AH1_AW1((uint16_t)0x7c00u) + #define A_INFN_H AH1_AW1((uint16_t)0xfc00u) + #else + #define A_INFP_H AH1_AW1(0x7c00u) + #define A_INFN_H AH1_AW1(0xfc00u) + #endif + +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ACpySgnH1(AH1 d,AH1 s){return AH1_AW1(AW1_AH1(d)|(AW1_AH1(s)&AW1_(0x8000u)));} + AH2 ACpySgnH2(AH2 d,AH2 s){return AH2_AW2(AW2_AH2(d)|(AW2_AH2(s)&AW2_(0x8000u)));} + AH3 ACpySgnH3(AH3 d,AH3 s){return AH3_AW3(AW3_AH3(d)|(AW3_AH3(s)&AW3_(0x8000u)));} + AH4 ACpySgnH4(AH4 d,AH4 s){return AH4_AW4(AW4_AH4(d)|(AW4_AH4(s)&AW4_(0x8000u)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ASignedH1(AH1 m){return ASatH1(m*AH1_(A_INFN_H));} + AH2 ASignedH2(AH2 m){return ASatH2(m*AH2_(A_INFN_H));} + AH3 ASignedH3(AH3 m){return ASatH3(m*AH3_(A_INFN_H));} + AH4 ASignedH4(AH4 m){return ASatH4(m*AH4_(A_INFN_H));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AGtZeroH1(AH1 m){return ASatH1(m*AH1_(A_INFP_H));} + AH2 AGtZeroH2(AH2 m){return ASatH2(m*AH2_(A_INFP_H));} + AH3 AGtZeroH3(AH3 m){return ASatH3(m*AH3_(A_INFP_H));} + AH4 AGtZeroH4(AH4 m){return ASatH4(m*AH4_(A_INFP_H));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// [FIS] FLOAT INTEGER SORTABLE +//------------------------------------------------------------------------------------------------------------------------------ +// Float to integer sortable. +// - If sign bit=0, flip the sign bit (positives). +// - If sign bit=1, flip all bits (negatives). +// Integer sortable to float. +// - If sign bit=1, flip the sign bit (positives). +// - If sign bit=0, flip all bits (negatives). +// Has nice side effects. +// - Larger integers are more positive values. +// - Float zero is mapped to center of integers (so clear to integer zero is a nice default for atomic max usage). +// Burns 3 ops for conversion {shift,or,xor}. +//============================================================================================================================== + AU1 AFisToU1(AU1 x){return x^(( AShrSU1(x,AU1_(31)))|AU1_(0x80000000));} + AU1 AFisFromU1(AU1 x){return x^((~AShrSU1(x,AU1_(31)))|AU1_(0x80000000));} +//------------------------------------------------------------------------------------------------------------------------------ + // Just adjust high 16-bit value (useful when upper part of 32-bit word is a 16-bit float value). + AU1 AFisToHiU1(AU1 x){return x^(( AShrSU1(x,AU1_(15)))|AU1_(0x80000000));} + AU1 AFisFromHiU1(AU1 x){return x^((~AShrSU1(x,AU1_(15)))|AU1_(0x80000000));} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_HALF + AW1 AFisToW1(AW1 x){return x^(( AShrSW1(x,AW1_(15)))|AW1_(0x8000));} + AW1 AFisFromW1(AW1 x){return x^((~AShrSW1(x,AW1_(15)))|AW1_(0x8000));} +//------------------------------------------------------------------------------------------------------------------------------ + AW2 AFisToW2(AW2 x){return x^(( AShrSW2(x,AW2_(15)))|AW2_(0x8000));} + AW2 AFisFromW2(AW2 x){return x^((~AShrSW2(x,AW2_(15)))|AW2_(0x8000));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// [PERM] V_PERM_B32 +//------------------------------------------------------------------------------------------------------------------------------ +// Support for V_PERM_B32 started in the 3rd generation of GCN. +//------------------------------------------------------------------------------------------------------------------------------ +// yyyyxxxx - The 'i' input. +// 76543210 +// ======== +// HGFEDCBA - Naming on permutation. +//------------------------------------------------------------------------------------------------------------------------------ +// TODO +// ==== +// - Make sure compiler optimizes this. +//============================================================================================================================== + #ifdef A_HALF + AU1 APerm0E0A(AU2 i){return((i.x )&0xffu)|((i.y<<16)&0xff0000u);} + AU1 APerm0F0B(AU2 i){return((i.x>> 8)&0xffu)|((i.y<< 8)&0xff0000u);} + AU1 APerm0G0C(AU2 i){return((i.x>>16)&0xffu)|((i.y )&0xff0000u);} + AU1 APerm0H0D(AU2 i){return((i.x>>24)&0xffu)|((i.y>> 8)&0xff0000u);} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 APermHGFA(AU2 i){return((i.x )&0x000000ffu)|(i.y&0xffffff00u);} + AU1 APermHGFC(AU2 i){return((i.x>>16)&0x000000ffu)|(i.y&0xffffff00u);} + AU1 APermHGAE(AU2 i){return((i.x<< 8)&0x0000ff00u)|(i.y&0xffff00ffu);} + AU1 APermHGCE(AU2 i){return((i.x>> 8)&0x0000ff00u)|(i.y&0xffff00ffu);} + AU1 APermHAFE(AU2 i){return((i.x<<16)&0x00ff0000u)|(i.y&0xff00ffffu);} + AU1 APermHCFE(AU2 i){return((i.x )&0x00ff0000u)|(i.y&0xff00ffffu);} + AU1 APermAGFE(AU2 i){return((i.x<<24)&0xff000000u)|(i.y&0x00ffffffu);} + AU1 APermCGFE(AU2 i){return((i.x<< 8)&0xff000000u)|(i.y&0x00ffffffu);} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 APermGCEA(AU2 i){return((i.x)&0x00ff00ffu)|((i.y<<8)&0xff00ff00u);} + AU1 APermGECA(AU2 i){return(((i.x)&0xffu)|((i.x>>8)&0xff00u)|((i.y<<16)&0xff0000u)|((i.y<<8)&0xff000000u));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// [BUC] BYTE UNSIGNED CONVERSION +//------------------------------------------------------------------------------------------------------------------------------ +// Designed to use the optimal conversion, enables the scaling to possibly be factored into other computation. +// Works on a range of {0 to A_BUC_<32,16>}, for <32-bit, and 16-bit> respectively. +//------------------------------------------------------------------------------------------------------------------------------ +// OPCODE NOTES +// ============ +// GCN does not do UNORM or SNORM for bytes in opcodes. +// - V_CVT_F32_UBYTE{0,1,2,3} - Unsigned byte to float. +// - V_CVT_PKACC_U8_F32 - Float to unsigned byte (does bit-field insert into 32-bit integer). +// V_PERM_B32 does byte packing with ability to zero fill bytes as well. +// - Can pull out byte values from two sources, and zero fill upper 8-bits of packed hi and lo. +//------------------------------------------------------------------------------------------------------------------------------ +// BYTE : FLOAT - ABuc{0,1,2,3}{To,From}U1() - Designed for V_CVT_F32_UBYTE* and V_CVT_PKACCUM_U8_F32 ops. +// ==== ===== +// 0 : 0 +// 1 : 1 +// ... +// 255 : 255 +// : 256 (just outside the encoding range) +//------------------------------------------------------------------------------------------------------------------------------ +// BYTE : FLOAT - ABuc{0,1,2,3}{To,From}U2() - Designed for 16-bit denormal tricks and V_PERM_B32. +// ==== ===== +// 0 : 0 +// 1 : 1/512 +// 2 : 1/256 +// ... +// 64 : 1/8 +// 128 : 1/4 +// 255 : 255/512 +// : 1/2 (just outside the encoding range) +//------------------------------------------------------------------------------------------------------------------------------ +// OPTIMAL IMPLEMENTATIONS ON AMD ARCHITECTURES +// ============================================ +// r=ABuc0FromU1(i) +// V_CVT_F32_UBYTE0 r,i +// -------------------------------------------- +// r=ABuc0ToU1(d,i) +// V_CVT_PKACCUM_U8_F32 r,i,0,d +// -------------------------------------------- +// d=ABuc0FromU2(i) +// Where 'k0' is an SGPR with 0x0E0A +// Where 'k1' is an SGPR with {32768.0} packed into the lower 16-bits +// V_PERM_B32 d,i.x,i.y,k0 +// V_PK_FMA_F16 d,d,k1.x,0 +// -------------------------------------------- +// r=ABuc0ToU2(d,i) +// Where 'k0' is an SGPR with {1.0/32768.0} packed into the lower 16-bits +// Where 'k1' is an SGPR with 0x???? +// Where 'k2' is an SGPR with 0x???? +// V_PK_FMA_F16 i,i,k0.x,0 +// V_PERM_B32 r.x,i,i,k1 +// V_PERM_B32 r.y,i,i,k2 +//============================================================================================================================== + // Peak range for 32-bit and 16-bit operations. + #define A_BUC_32 (255.0) + #define A_BUC_16 (255.0/512.0) +//============================================================================================================================== + #if 1 + // Designed to be one V_CVT_PKACCUM_U8_F32. + // The extra min is required to pattern match to V_CVT_PKACCUM_U8_F32. + AU1 ABuc0ToU1(AU1 d,AF1 i){return (d&0xffffff00u)|((min(AU1(i),255u) )&(0x000000ffu));} + AU1 ABuc1ToU1(AU1 d,AF1 i){return (d&0xffff00ffu)|((min(AU1(i),255u)<< 8)&(0x0000ff00u));} + AU1 ABuc2ToU1(AU1 d,AF1 i){return (d&0xff00ffffu)|((min(AU1(i),255u)<<16)&(0x00ff0000u));} + AU1 ABuc3ToU1(AU1 d,AF1 i){return (d&0x00ffffffu)|((min(AU1(i),255u)<<24)&(0xff000000u));} +//------------------------------------------------------------------------------------------------------------------------------ + // Designed to be one V_CVT_F32_UBYTE*. + AF1 ABuc0FromU1(AU1 i){return AF1((i )&255u);} + AF1 ABuc1FromU1(AU1 i){return AF1((i>> 8)&255u);} + AF1 ABuc2FromU1(AU1 i){return AF1((i>>16)&255u);} + AF1 ABuc3FromU1(AU1 i){return AF1((i>>24)&255u);} + #endif +//============================================================================================================================== + #ifdef A_HALF + // Takes {x0,x1} and {y0,y1} and builds {{x0,y0},{x1,y1}}. + AW2 ABuc01ToW2(AH2 x,AH2 y){x*=AH2_(1.0/32768.0);y*=AH2_(1.0/32768.0); + return AW2_AU1(APermGCEA(AU2(AU1_AW2(AW2_AH2(x)),AU1_AW2(AW2_AH2(y)))));} +//------------------------------------------------------------------------------------------------------------------------------ + // Designed for 3 ops to do SOA to AOS and conversion. + AU2 ABuc0ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0))); + return AU2(APermHGFA(AU2(d.x,b)),APermHGFC(AU2(d.y,b)));} + AU2 ABuc1ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0))); + return AU2(APermHGAE(AU2(d.x,b)),APermHGCE(AU2(d.y,b)));} + AU2 ABuc2ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0))); + return AU2(APermHAFE(AU2(d.x,b)),APermHCFE(AU2(d.y,b)));} + AU2 ABuc3ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0))); + return AU2(APermAGFE(AU2(d.x,b)),APermCGFE(AU2(d.y,b)));} +//------------------------------------------------------------------------------------------------------------------------------ + // Designed for 2 ops to do both AOS to SOA, and conversion. + AH2 ABuc0FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0E0A(i)))*AH2_(32768.0);} + AH2 ABuc1FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0F0B(i)))*AH2_(32768.0);} + AH2 ABuc2FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0G0C(i)))*AH2_(32768.0);} + AH2 ABuc3FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0H0D(i)))*AH2_(32768.0);} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// [BSC] BYTE SIGNED CONVERSION +//------------------------------------------------------------------------------------------------------------------------------ +// Similar to [BUC]. +// Works on a range of {-/+ A_BSC_<32,16>}, for <32-bit, and 16-bit> respectively. +//------------------------------------------------------------------------------------------------------------------------------ +// ENCODING (without zero-based encoding) +// ======== +// 0 = unused (can be used to mean something else) +// 1 = lowest value +// 128 = exact zero center (zero based encoding +// 255 = highest value +//------------------------------------------------------------------------------------------------------------------------------ +// Zero-based [Zb] flips the MSB bit of the byte (making 128 "exact zero" actually zero). +// This is useful if there is a desire for cleared values to decode as zero. +//------------------------------------------------------------------------------------------------------------------------------ +// BYTE : FLOAT - ABsc{0,1,2,3}{To,From}U2() - Designed for 16-bit denormal tricks and V_PERM_B32. +// ==== ===== +// 0 : -127/512 (unused) +// 1 : -126/512 +// 2 : -125/512 +// ... +// 128 : 0 +// ... +// 255 : 127/512 +// : 1/4 (just outside the encoding range) +//============================================================================================================================== + // Peak range for 32-bit and 16-bit operations. + #define A_BSC_32 (127.0) + #define A_BSC_16 (127.0/512.0) +//============================================================================================================================== + #if 1 + AU1 ABsc0ToU1(AU1 d,AF1 i){return (d&0xffffff00u)|((min(AU1(i+128.0),255u) )&(0x000000ffu));} + AU1 ABsc1ToU1(AU1 d,AF1 i){return (d&0xffff00ffu)|((min(AU1(i+128.0),255u)<< 8)&(0x0000ff00u));} + AU1 ABsc2ToU1(AU1 d,AF1 i){return (d&0xff00ffffu)|((min(AU1(i+128.0),255u)<<16)&(0x00ff0000u));} + AU1 ABsc3ToU1(AU1 d,AF1 i){return (d&0x00ffffffu)|((min(AU1(i+128.0),255u)<<24)&(0xff000000u));} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 ABsc0ToZbU1(AU1 d,AF1 i){return ((d&0xffffff00u)|((min(AU1(trunc(i)+128.0),255u) )&(0x000000ffu)))^0x00000080u;} + AU1 ABsc1ToZbU1(AU1 d,AF1 i){return ((d&0xffff00ffu)|((min(AU1(trunc(i)+128.0),255u)<< 8)&(0x0000ff00u)))^0x00008000u;} + AU1 ABsc2ToZbU1(AU1 d,AF1 i){return ((d&0xff00ffffu)|((min(AU1(trunc(i)+128.0),255u)<<16)&(0x00ff0000u)))^0x00800000u;} + AU1 ABsc3ToZbU1(AU1 d,AF1 i){return ((d&0x00ffffffu)|((min(AU1(trunc(i)+128.0),255u)<<24)&(0xff000000u)))^0x80000000u;} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 ABsc0FromU1(AU1 i){return AF1((i )&255u)-128.0;} + AF1 ABsc1FromU1(AU1 i){return AF1((i>> 8)&255u)-128.0;} + AF1 ABsc2FromU1(AU1 i){return AF1((i>>16)&255u)-128.0;} + AF1 ABsc3FromU1(AU1 i){return AF1((i>>24)&255u)-128.0;} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 ABsc0FromZbU1(AU1 i){return AF1(((i )&255u)^0x80u)-128.0;} + AF1 ABsc1FromZbU1(AU1 i){return AF1(((i>> 8)&255u)^0x80u)-128.0;} + AF1 ABsc2FromZbU1(AU1 i){return AF1(((i>>16)&255u)^0x80u)-128.0;} + AF1 ABsc3FromZbU1(AU1 i){return AF1(((i>>24)&255u)^0x80u)-128.0;} + #endif +//============================================================================================================================== + #ifdef A_HALF + // Takes {x0,x1} and {y0,y1} and builds {{x0,y0},{x1,y1}}. + AW2 ABsc01ToW2(AH2 x,AH2 y){x=x*AH2_(1.0/32768.0)+AH2_(0.25/32768.0);y=y*AH2_(1.0/32768.0)+AH2_(0.25/32768.0); + return AW2_AU1(APermGCEA(AU2(AU1_AW2(AW2_AH2(x)),AU1_AW2(AW2_AH2(y)))));} +//------------------------------------------------------------------------------------------------------------------------------ + AU2 ABsc0ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0))); + return AU2(APermHGFA(AU2(d.x,b)),APermHGFC(AU2(d.y,b)));} + AU2 ABsc1ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0))); + return AU2(APermHGAE(AU2(d.x,b)),APermHGCE(AU2(d.y,b)));} + AU2 ABsc2ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0))); + return AU2(APermHAFE(AU2(d.x,b)),APermHCFE(AU2(d.y,b)));} + AU2 ABsc3ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0))); + return AU2(APermAGFE(AU2(d.x,b)),APermCGFE(AU2(d.y,b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AU2 ABsc0ToZbU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0)))^0x00800080u; + return AU2(APermHGFA(AU2(d.x,b)),APermHGFC(AU2(d.y,b)));} + AU2 ABsc1ToZbU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0)))^0x00800080u; + return AU2(APermHGAE(AU2(d.x,b)),APermHGCE(AU2(d.y,b)));} + AU2 ABsc2ToZbU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0)))^0x00800080u; + return AU2(APermHAFE(AU2(d.x,b)),APermHCFE(AU2(d.y,b)));} + AU2 ABsc3ToZbU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0)))^0x00800080u; + return AU2(APermAGFE(AU2(d.x,b)),APermCGFE(AU2(d.y,b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH2 ABsc0FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0E0A(i)))*AH2_(32768.0)-AH2_(0.25);} + AH2 ABsc1FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0F0B(i)))*AH2_(32768.0)-AH2_(0.25);} + AH2 ABsc2FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0G0C(i)))*AH2_(32768.0)-AH2_(0.25);} + AH2 ABsc3FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0H0D(i)))*AH2_(32768.0)-AH2_(0.25);} +//------------------------------------------------------------------------------------------------------------------------------ + AH2 ABsc0FromZbU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0E0A(i)^0x00800080u))*AH2_(32768.0)-AH2_(0.25);} + AH2 ABsc1FromZbU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0F0B(i)^0x00800080u))*AH2_(32768.0)-AH2_(0.25);} + AH2 ABsc2FromZbU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0G0C(i)^0x00800080u))*AH2_(32768.0)-AH2_(0.25);} + AH2 ABsc3FromZbU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0H0D(i)^0x00800080u))*AH2_(32768.0)-AH2_(0.25);} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// HALF APPROXIMATIONS +//------------------------------------------------------------------------------------------------------------------------------ +// These support only positive inputs. +// Did not see value yet in specialization for range. +// Using quick testing, ended up mostly getting the same "best" approximation for various ranges. +// With hardware that can co-execute transcendentals, the value in approximations could be less than expected. +// However from a latency perspective, if execution of a transcendental is 4 clk, with no packed support, -> 8 clk total. +// And co-execution would require a compiler interleaving a lot of independent work for packed usage. +//------------------------------------------------------------------------------------------------------------------------------ +// The one Newton Raphson iteration form of rsq() was skipped (requires 6 ops total). +// Same with sqrt(), as this could be x*rsq() (7 ops). +//============================================================================================================================== + #ifdef A_HALF + // Minimize squared error across full positive range, 2 ops. + // The 0x1de2 based approximation maps {0 to 1} input maps to < 1 output. + AH1 APrxLoSqrtH1(AH1 a){return AH1_AW1((AW1_AH1(a)>>AW1_(1))+AW1_(0x1de2));} + AH2 APrxLoSqrtH2(AH2 a){return AH2_AW2((AW2_AH2(a)>>AW2_(1))+AW2_(0x1de2));} + AH3 APrxLoSqrtH3(AH3 a){return AH3_AW3((AW3_AH3(a)>>AW3_(1))+AW3_(0x1de2));} + AH4 APrxLoSqrtH4(AH4 a){return AH4_AW4((AW4_AH4(a)>>AW4_(1))+AW4_(0x1de2));} +//------------------------------------------------------------------------------------------------------------------------------ + // Lower precision estimation, 1 op. + // Minimize squared error across {smallest normal to 16384.0}. + AH1 APrxLoRcpH1(AH1 a){return AH1_AW1(AW1_(0x7784)-AW1_AH1(a));} + AH2 APrxLoRcpH2(AH2 a){return AH2_AW2(AW2_(0x7784)-AW2_AH2(a));} + AH3 APrxLoRcpH3(AH3 a){return AH3_AW3(AW3_(0x7784)-AW3_AH3(a));} + AH4 APrxLoRcpH4(AH4 a){return AH4_AW4(AW4_(0x7784)-AW4_AH4(a));} +//------------------------------------------------------------------------------------------------------------------------------ + // Medium precision estimation, one Newton Raphson iteration, 3 ops. + AH1 APrxMedRcpH1(AH1 a){AH1 b=AH1_AW1(AW1_(0x778d)-AW1_AH1(a));return b*(-b*a+AH1_(2.0));} + AH2 APrxMedRcpH2(AH2 a){AH2 b=AH2_AW2(AW2_(0x778d)-AW2_AH2(a));return b*(-b*a+AH2_(2.0));} + AH3 APrxMedRcpH3(AH3 a){AH3 b=AH3_AW3(AW3_(0x778d)-AW3_AH3(a));return b*(-b*a+AH3_(2.0));} + AH4 APrxMedRcpH4(AH4 a){AH4 b=AH4_AW4(AW4_(0x778d)-AW4_AH4(a));return b*(-b*a+AH4_(2.0));} +//------------------------------------------------------------------------------------------------------------------------------ + // Minimize squared error across {smallest normal to 16384.0}, 2 ops. + AH1 APrxLoRsqH1(AH1 a){return AH1_AW1(AW1_(0x59a3)-(AW1_AH1(a)>>AW1_(1)));} + AH2 APrxLoRsqH2(AH2 a){return AH2_AW2(AW2_(0x59a3)-(AW2_AH2(a)>>AW2_(1)));} + AH3 APrxLoRsqH3(AH3 a){return AH3_AW3(AW3_(0x59a3)-(AW3_AH3(a)>>AW3_(1)));} + AH4 APrxLoRsqH4(AH4 a){return AH4_AW4(AW4_(0x59a3)-(AW4_AH4(a)>>AW4_(1)));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// FLOAT APPROXIMATIONS +//------------------------------------------------------------------------------------------------------------------------------ +// Michal Drobot has an excellent presentation on these: "Low Level Optimizations For GCN", +// - Idea dates back to SGI, then to Quake 3, etc. +// - https://michaldrobot.files.wordpress.com/2014/05/gcn_alu_opt_digitaldragons2014.pdf +// - sqrt(x)=rsqrt(x)*x +// - rcp(x)=rsqrt(x)*rsqrt(x) for positive x +// - https://github.com/michaldrobot/ShaderFastLibs/blob/master/ShaderFastMathLib.h +//------------------------------------------------------------------------------------------------------------------------------ +// These below are from perhaps less complete searching for optimal. +// Used FP16 normal range for testing with +4096 32-bit step size for sampling error. +// So these match up well with the half approximations. +//============================================================================================================================== + AF1 APrxLoSqrtF1(AF1 a){return AF1_AU1((AU1_AF1(a)>>AU1_(1))+AU1_(0x1fbc4639));} + AF1 APrxLoRcpF1(AF1 a){return AF1_AU1(AU1_(0x7ef07ebb)-AU1_AF1(a));} + AF1 APrxMedRcpF1(AF1 a){AF1 b=AF1_AU1(AU1_(0x7ef19fff)-AU1_AF1(a));return b*(-b*a+AF1_(2.0));} + AF1 APrxLoRsqF1(AF1 a){return AF1_AU1(AU1_(0x5f347d74)-(AU1_AF1(a)>>AU1_(1)));} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 APrxLoSqrtF2(AF2 a){return AF2_AU2((AU2_AF2(a)>>AU2_(1))+AU2_(0x1fbc4639));} + AF2 APrxLoRcpF2(AF2 a){return AF2_AU2(AU2_(0x7ef07ebb)-AU2_AF2(a));} + AF2 APrxMedRcpF2(AF2 a){AF2 b=AF2_AU2(AU2_(0x7ef19fff)-AU2_AF2(a));return b*(-b*a+AF2_(2.0));} + AF2 APrxLoRsqF2(AF2 a){return AF2_AU2(AU2_(0x5f347d74)-(AU2_AF2(a)>>AU2_(1)));} +//------------------------------------------------------------------------------------------------------------------------------ + AF3 APrxLoSqrtF3(AF3 a){return AF3_AU3((AU3_AF3(a)>>AU3_(1))+AU3_(0x1fbc4639));} + AF3 APrxLoRcpF3(AF3 a){return AF3_AU3(AU3_(0x7ef07ebb)-AU3_AF3(a));} + AF3 APrxMedRcpF3(AF3 a){AF3 b=AF3_AU3(AU3_(0x7ef19fff)-AU3_AF3(a));return b*(-b*a+AF3_(2.0));} + AF3 APrxLoRsqF3(AF3 a){return AF3_AU3(AU3_(0x5f347d74)-(AU3_AF3(a)>>AU3_(1)));} +//------------------------------------------------------------------------------------------------------------------------------ + AF4 APrxLoSqrtF4(AF4 a){return AF4_AU4((AU4_AF4(a)>>AU4_(1))+AU4_(0x1fbc4639));} + AF4 APrxLoRcpF4(AF4 a){return AF4_AU4(AU4_(0x7ef07ebb)-AU4_AF4(a));} + AF4 APrxMedRcpF4(AF4 a){AF4 b=AF4_AU4(AU4_(0x7ef19fff)-AU4_AF4(a));return b*(-b*a+AF4_(2.0));} + AF4 APrxLoRsqF4(AF4 a){return AF4_AU4(AU4_(0x5f347d74)-(AU4_AF4(a)>>AU4_(1)));} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// PQ APPROXIMATIONS +//------------------------------------------------------------------------------------------------------------------------------ +// PQ is very close to x^(1/8). The functions below Use the fast float approximation method to do +// PQ<~>Gamma2 (4th power and fast 4th root) and PQ<~>Linear (8th power and fast 8th root). Maximum error is ~0.2%. +//============================================================================================================================== +// Helpers + AF1 Quart(AF1 a) { a = a * a; return a * a;} + AF1 Oct(AF1 a) { a = a * a; a = a * a; return a * a; } + AF2 Quart(AF2 a) { a = a * a; return a * a; } + AF2 Oct(AF2 a) { a = a * a; a = a * a; return a * a; } + AF3 Quart(AF3 a) { a = a * a; return a * a; } + AF3 Oct(AF3 a) { a = a * a; a = a * a; return a * a; } + AF4 Quart(AF4 a) { a = a * a; return a * a; } + AF4 Oct(AF4 a) { a = a * a; a = a * a; return a * a; } + //------------------------------------------------------------------------------------------------------------------------------ + AF1 APrxPQToGamma2(AF1 a) { return Quart(a); } + AF1 APrxPQToLinear(AF1 a) { return Oct(a); } + AF1 APrxLoGamma2ToPQ(AF1 a) { return AF1_AU1((AU1_AF1(a) >> AU1_(2)) + AU1_(0x2F9A4E46)); } + AF1 APrxMedGamma2ToPQ(AF1 a) { AF1 b = AF1_AU1((AU1_AF1(a) >> AU1_(2)) + AU1_(0x2F9A4E46)); AF1 b4 = Quart(b); return b - b * (b4 - a) / (AF1_(4.0) * b4); } + AF1 APrxHighGamma2ToPQ(AF1 a) { return sqrt(sqrt(a)); } + AF1 APrxLoLinearToPQ(AF1 a) { return AF1_AU1((AU1_AF1(a) >> AU1_(3)) + AU1_(0x378D8723)); } + AF1 APrxMedLinearToPQ(AF1 a) { AF1 b = AF1_AU1((AU1_AF1(a) >> AU1_(3)) + AU1_(0x378D8723)); AF1 b8 = Oct(b); return b - b * (b8 - a) / (AF1_(8.0) * b8); } + AF1 APrxHighLinearToPQ(AF1 a) { return sqrt(sqrt(sqrt(a))); } + //------------------------------------------------------------------------------------------------------------------------------ + AF2 APrxPQToGamma2(AF2 a) { return Quart(a); } + AF2 APrxPQToLinear(AF2 a) { return Oct(a); } + AF2 APrxLoGamma2ToPQ(AF2 a) { return AF2_AU2((AU2_AF2(a) >> AU2_(2)) + AU2_(0x2F9A4E46)); } + AF2 APrxMedGamma2ToPQ(AF2 a) { AF2 b = AF2_AU2((AU2_AF2(a) >> AU2_(2)) + AU2_(0x2F9A4E46)); AF2 b4 = Quart(b); return b - b * (b4 - a) / (AF1_(4.0) * b4); } + AF2 APrxHighGamma2ToPQ(AF2 a) { return sqrt(sqrt(a)); } + AF2 APrxLoLinearToPQ(AF2 a) { return AF2_AU2((AU2_AF2(a) >> AU2_(3)) + AU2_(0x378D8723)); } + AF2 APrxMedLinearToPQ(AF2 a) { AF2 b = AF2_AU2((AU2_AF2(a) >> AU2_(3)) + AU2_(0x378D8723)); AF2 b8 = Oct(b); return b - b * (b8 - a) / (AF1_(8.0) * b8); } + AF2 APrxHighLinearToPQ(AF2 a) { return sqrt(sqrt(sqrt(a))); } + //------------------------------------------------------------------------------------------------------------------------------ + AF3 APrxPQToGamma2(AF3 a) { return Quart(a); } + AF3 APrxPQToLinear(AF3 a) { return Oct(a); } + AF3 APrxLoGamma2ToPQ(AF3 a) { return AF3_AU3((AU3_AF3(a) >> AU3_(2)) + AU3_(0x2F9A4E46)); } + AF3 APrxMedGamma2ToPQ(AF3 a) { AF3 b = AF3_AU3((AU3_AF3(a) >> AU3_(2)) + AU3_(0x2F9A4E46)); AF3 b4 = Quart(b); return b - b * (b4 - a) / (AF1_(4.0) * b4); } + AF3 APrxHighGamma2ToPQ(AF3 a) { return sqrt(sqrt(a)); } + AF3 APrxLoLinearToPQ(AF3 a) { return AF3_AU3((AU3_AF3(a) >> AU3_(3)) + AU3_(0x378D8723)); } + AF3 APrxMedLinearToPQ(AF3 a) { AF3 b = AF3_AU3((AU3_AF3(a) >> AU3_(3)) + AU3_(0x378D8723)); AF3 b8 = Oct(b); return b - b * (b8 - a) / (AF1_(8.0) * b8); } + AF3 APrxHighLinearToPQ(AF3 a) { return sqrt(sqrt(sqrt(a))); } + //------------------------------------------------------------------------------------------------------------------------------ + AF4 APrxPQToGamma2(AF4 a) { return Quart(a); } + AF4 APrxPQToLinear(AF4 a) { return Oct(a); } + AF4 APrxLoGamma2ToPQ(AF4 a) { return AF4_AU4((AU4_AF4(a) >> AU4_(2)) + AU4_(0x2F9A4E46)); } + AF4 APrxMedGamma2ToPQ(AF4 a) { AF4 b = AF4_AU4((AU4_AF4(a) >> AU4_(2)) + AU4_(0x2F9A4E46)); AF4 b4 = Quart(b); return b - b * (b4 - a) / (AF1_(4.0) * b4); } + AF4 APrxHighGamma2ToPQ(AF4 a) { return sqrt(sqrt(a)); } + AF4 APrxLoLinearToPQ(AF4 a) { return AF4_AU4((AU4_AF4(a) >> AU4_(3)) + AU4_(0x378D8723)); } + AF4 APrxMedLinearToPQ(AF4 a) { AF4 b = AF4_AU4((AU4_AF4(a) >> AU4_(3)) + AU4_(0x378D8723)); AF4 b8 = Oct(b); return b - b * (b8 - a) / (AF1_(8.0) * b8); } + AF4 APrxHighLinearToPQ(AF4 a) { return sqrt(sqrt(sqrt(a))); } +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// PARABOLIC SIN & COS +//------------------------------------------------------------------------------------------------------------------------------ +// Approximate answers to transcendental questions. +//------------------------------------------------------------------------------------------------------------------------------ +//============================================================================================================================== + #if 1 + // Valid input range is {-1 to 1} representing {0 to 2 pi}. + // Output range is {-1/4 to 1/4} representing {-1 to 1}. + AF1 APSinF1(AF1 x){return x*abs(x)-x;} // MAD. + AF2 APSinF2(AF2 x){return x*abs(x)-x;} + AF1 APCosF1(AF1 x){x=AFractF1(x*AF1_(0.5)+AF1_(0.75));x=x*AF1_(2.0)-AF1_(1.0);return APSinF1(x);} // 3x MAD, FRACT + AF2 APCosF2(AF2 x){x=AFractF2(x*AF2_(0.5)+AF2_(0.75));x=x*AF2_(2.0)-AF2_(1.0);return APSinF2(x);} + AF2 APSinCosF1(AF1 x){AF1 y=AFractF1(x*AF1_(0.5)+AF1_(0.75));y=y*AF1_(2.0)-AF1_(1.0);return APSinF2(AF2(x,y));} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_HALF + // For a packed {sin,cos} pair, + // - Native takes 16 clocks and 4 issue slots (no packed transcendentals). + // - Parabolic takes 8 clocks and 8 issue slots (only fract is non-packed). + AH1 APSinH1(AH1 x){return x*abs(x)-x;} + AH2 APSinH2(AH2 x){return x*abs(x)-x;} // AND,FMA + AH1 APCosH1(AH1 x){x=AFractH1(x*AH1_(0.5)+AH1_(0.75));x=x*AH1_(2.0)-AH1_(1.0);return APSinH1(x);} + AH2 APCosH2(AH2 x){x=AFractH2(x*AH2_(0.5)+AH2_(0.75));x=x*AH2_(2.0)-AH2_(1.0);return APSinH2(x);} // 3x FMA, 2xFRACT, AND + AH2 APSinCosH1(AH1 x){AH1 y=AFractH1(x*AH1_(0.5)+AH1_(0.75));y=y*AH1_(2.0)-AH1_(1.0);return APSinH2(AH2(x,y));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// [ZOL] ZERO ONE LOGIC +//------------------------------------------------------------------------------------------------------------------------------ +// Conditional free logic designed for easy 16-bit packing, and backwards porting to 32-bit. +//------------------------------------------------------------------------------------------------------------------------------ +// 0 := false +// 1 := true +//------------------------------------------------------------------------------------------------------------------------------ +// AndNot(x,y) -> !(x&y) .... One op. +// AndOr(x,y,z) -> (x&y)|z ... One op. +// GtZero(x) -> x>0.0 ..... One op. +// Sel(x,y,z) -> x?y:z ..... Two ops, has no precision loss. +// Signed(x) -> x<0.0 ..... One op. +// ZeroPass(x,y) -> x?0:y ..... Two ops, 'y' is a pass through safe for aliasing as integer. +//------------------------------------------------------------------------------------------------------------------------------ +// OPTIMIZATION NOTES +// ================== +// - On Vega to use 2 constants in a packed op, pass in as one AW2 or one AH2 'k.xy' and use as 'k.xx' and 'k.yy'. +// For example 'a.xy*k.xx+k.yy'. +//============================================================================================================================== + #if 1 + AU1 AZolAndU1(AU1 x,AU1 y){return min(x,y);} + AU2 AZolAndU2(AU2 x,AU2 y){return min(x,y);} + AU3 AZolAndU3(AU3 x,AU3 y){return min(x,y);} + AU4 AZolAndU4(AU4 x,AU4 y){return min(x,y);} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AZolNotU1(AU1 x){return x^AU1_(1);} + AU2 AZolNotU2(AU2 x){return x^AU2_(1);} + AU3 AZolNotU3(AU3 x){return x^AU3_(1);} + AU4 AZolNotU4(AU4 x){return x^AU4_(1);} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AZolOrU1(AU1 x,AU1 y){return max(x,y);} + AU2 AZolOrU2(AU2 x,AU2 y){return max(x,y);} + AU3 AZolOrU3(AU3 x,AU3 y){return max(x,y);} + AU4 AZolOrU4(AU4 x,AU4 y){return max(x,y);} +//============================================================================================================================== + AU1 AZolF1ToU1(AF1 x){return AU1(x);} + AU2 AZolF2ToU2(AF2 x){return AU2(x);} + AU3 AZolF3ToU3(AF3 x){return AU3(x);} + AU4 AZolF4ToU4(AF4 x){return AU4(x);} +//------------------------------------------------------------------------------------------------------------------------------ + // 2 ops, denormals don't work in 32-bit on PC (and if they are enabled, OMOD is disabled). + AU1 AZolNotF1ToU1(AF1 x){return AU1(AF1_(1.0)-x);} + AU2 AZolNotF2ToU2(AF2 x){return AU2(AF2_(1.0)-x);} + AU3 AZolNotF3ToU3(AF3 x){return AU3(AF3_(1.0)-x);} + AU4 AZolNotF4ToU4(AF4 x){return AU4(AF4_(1.0)-x);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolU1ToF1(AU1 x){return AF1(x);} + AF2 AZolU2ToF2(AU2 x){return AF2(x);} + AF3 AZolU3ToF3(AU3 x){return AF3(x);} + AF4 AZolU4ToF4(AU4 x){return AF4(x);} +//============================================================================================================================== + AF1 AZolAndF1(AF1 x,AF1 y){return min(x,y);} + AF2 AZolAndF2(AF2 x,AF2 y){return min(x,y);} + AF3 AZolAndF3(AF3 x,AF3 y){return min(x,y);} + AF4 AZolAndF4(AF4 x,AF4 y){return min(x,y);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 ASolAndNotF1(AF1 x,AF1 y){return (-x)*y+AF1_(1.0);} + AF2 ASolAndNotF2(AF2 x,AF2 y){return (-x)*y+AF2_(1.0);} + AF3 ASolAndNotF3(AF3 x,AF3 y){return (-x)*y+AF3_(1.0);} + AF4 ASolAndNotF4(AF4 x,AF4 y){return (-x)*y+AF4_(1.0);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolAndOrF1(AF1 x,AF1 y,AF1 z){return ASatF1(x*y+z);} + AF2 AZolAndOrF2(AF2 x,AF2 y,AF2 z){return ASatF2(x*y+z);} + AF3 AZolAndOrF3(AF3 x,AF3 y,AF3 z){return ASatF3(x*y+z);} + AF4 AZolAndOrF4(AF4 x,AF4 y,AF4 z){return ASatF4(x*y+z);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolGtZeroF1(AF1 x){return ASatF1(x*AF1_(A_INFP_F));} + AF2 AZolGtZeroF2(AF2 x){return ASatF2(x*AF2_(A_INFP_F));} + AF3 AZolGtZeroF3(AF3 x){return ASatF3(x*AF3_(A_INFP_F));} + AF4 AZolGtZeroF4(AF4 x){return ASatF4(x*AF4_(A_INFP_F));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolNotF1(AF1 x){return AF1_(1.0)-x;} + AF2 AZolNotF2(AF2 x){return AF2_(1.0)-x;} + AF3 AZolNotF3(AF3 x){return AF3_(1.0)-x;} + AF4 AZolNotF4(AF4 x){return AF4_(1.0)-x;} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolOrF1(AF1 x,AF1 y){return max(x,y);} + AF2 AZolOrF2(AF2 x,AF2 y){return max(x,y);} + AF3 AZolOrF3(AF3 x,AF3 y){return max(x,y);} + AF4 AZolOrF4(AF4 x,AF4 y){return max(x,y);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolSelF1(AF1 x,AF1 y,AF1 z){AF1 r=(-x)*z+z;return x*y+r;} + AF2 AZolSelF2(AF2 x,AF2 y,AF2 z){AF2 r=(-x)*z+z;return x*y+r;} + AF3 AZolSelF3(AF3 x,AF3 y,AF3 z){AF3 r=(-x)*z+z;return x*y+r;} + AF4 AZolSelF4(AF4 x,AF4 y,AF4 z){AF4 r=(-x)*z+z;return x*y+r;} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolSignedF1(AF1 x){return ASatF1(x*AF1_(A_INFN_F));} + AF2 AZolSignedF2(AF2 x){return ASatF2(x*AF2_(A_INFN_F));} + AF3 AZolSignedF3(AF3 x){return ASatF3(x*AF3_(A_INFN_F));} + AF4 AZolSignedF4(AF4 x){return ASatF4(x*AF4_(A_INFN_F));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolZeroPassF1(AF1 x,AF1 y){return AF1_AU1((AU1_AF1(x)!=AU1_(0))?AU1_(0):AU1_AF1(y));} + AF2 AZolZeroPassF2(AF2 x,AF2 y){return AF2_AU2((AU2_AF2(x)!=AU2_(0))?AU2_(0):AU2_AF2(y));} + AF3 AZolZeroPassF3(AF3 x,AF3 y){return AF3_AU3((AU3_AF3(x)!=AU3_(0))?AU3_(0):AU3_AF3(y));} + AF4 AZolZeroPassF4(AF4 x,AF4 y){return AF4_AU4((AU4_AF4(x)!=AU4_(0))?AU4_(0):AU4_AF4(y));} + #endif +//============================================================================================================================== + #ifdef A_HALF + AW1 AZolAndW1(AW1 x,AW1 y){return min(x,y);} + AW2 AZolAndW2(AW2 x,AW2 y){return min(x,y);} + AW3 AZolAndW3(AW3 x,AW3 y){return min(x,y);} + AW4 AZolAndW4(AW4 x,AW4 y){return min(x,y);} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AZolNotW1(AW1 x){return x^AW1_(1);} + AW2 AZolNotW2(AW2 x){return x^AW2_(1);} + AW3 AZolNotW3(AW3 x){return x^AW3_(1);} + AW4 AZolNotW4(AW4 x){return x^AW4_(1);} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AZolOrW1(AW1 x,AW1 y){return max(x,y);} + AW2 AZolOrW2(AW2 x,AW2 y){return max(x,y);} + AW3 AZolOrW3(AW3 x,AW3 y){return max(x,y);} + AW4 AZolOrW4(AW4 x,AW4 y){return max(x,y);} +//============================================================================================================================== + // Uses denormal trick. + AW1 AZolH1ToW1(AH1 x){return AW1_AH1(x*AH1_AW1(AW1_(1)));} + AW2 AZolH2ToW2(AH2 x){return AW2_AH2(x*AH2_AW2(AW2_(1)));} + AW3 AZolH3ToW3(AH3 x){return AW3_AH3(x*AH3_AW3(AW3_(1)));} + AW4 AZolH4ToW4(AH4 x){return AW4_AH4(x*AH4_AW4(AW4_(1)));} +//------------------------------------------------------------------------------------------------------------------------------ + // AMD arch lacks a packed conversion opcode. + AH1 AZolW1ToH1(AW1 x){return AH1_AW1(x*AW1_AH1(AH1_(1.0)));} + AH2 AZolW2ToH2(AW2 x){return AH2_AW2(x*AW2_AH2(AH2_(1.0)));} + AH3 AZolW1ToH3(AW3 x){return AH3_AW3(x*AW3_AH3(AH3_(1.0)));} + AH4 AZolW2ToH4(AW4 x){return AH4_AW4(x*AW4_AH4(AH4_(1.0)));} +//============================================================================================================================== + AH1 AZolAndH1(AH1 x,AH1 y){return min(x,y);} + AH2 AZolAndH2(AH2 x,AH2 y){return min(x,y);} + AH3 AZolAndH3(AH3 x,AH3 y){return min(x,y);} + AH4 AZolAndH4(AH4 x,AH4 y){return min(x,y);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ASolAndNotH1(AH1 x,AH1 y){return (-x)*y+AH1_(1.0);} + AH2 ASolAndNotH2(AH2 x,AH2 y){return (-x)*y+AH2_(1.0);} + AH3 ASolAndNotH3(AH3 x,AH3 y){return (-x)*y+AH3_(1.0);} + AH4 ASolAndNotH4(AH4 x,AH4 y){return (-x)*y+AH4_(1.0);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AZolAndOrH1(AH1 x,AH1 y,AH1 z){return ASatH1(x*y+z);} + AH2 AZolAndOrH2(AH2 x,AH2 y,AH2 z){return ASatH2(x*y+z);} + AH3 AZolAndOrH3(AH3 x,AH3 y,AH3 z){return ASatH3(x*y+z);} + AH4 AZolAndOrH4(AH4 x,AH4 y,AH4 z){return ASatH4(x*y+z);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AZolGtZeroH1(AH1 x){return ASatH1(x*AH1_(A_INFP_H));} + AH2 AZolGtZeroH2(AH2 x){return ASatH2(x*AH2_(A_INFP_H));} + AH3 AZolGtZeroH3(AH3 x){return ASatH3(x*AH3_(A_INFP_H));} + AH4 AZolGtZeroH4(AH4 x){return ASatH4(x*AH4_(A_INFP_H));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AZolNotH1(AH1 x){return AH1_(1.0)-x;} + AH2 AZolNotH2(AH2 x){return AH2_(1.0)-x;} + AH3 AZolNotH3(AH3 x){return AH3_(1.0)-x;} + AH4 AZolNotH4(AH4 x){return AH4_(1.0)-x;} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AZolOrH1(AH1 x,AH1 y){return max(x,y);} + AH2 AZolOrH2(AH2 x,AH2 y){return max(x,y);} + AH3 AZolOrH3(AH3 x,AH3 y){return max(x,y);} + AH4 AZolOrH4(AH4 x,AH4 y){return max(x,y);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AZolSelH1(AH1 x,AH1 y,AH1 z){AH1 r=(-x)*z+z;return x*y+r;} + AH2 AZolSelH2(AH2 x,AH2 y,AH2 z){AH2 r=(-x)*z+z;return x*y+r;} + AH3 AZolSelH3(AH3 x,AH3 y,AH3 z){AH3 r=(-x)*z+z;return x*y+r;} + AH4 AZolSelH4(AH4 x,AH4 y,AH4 z){AH4 r=(-x)*z+z;return x*y+r;} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AZolSignedH1(AH1 x){return ASatH1(x*AH1_(A_INFN_H));} + AH2 AZolSignedH2(AH2 x){return ASatH2(x*AH2_(A_INFN_H));} + AH3 AZolSignedH3(AH3 x){return ASatH3(x*AH3_(A_INFN_H));} + AH4 AZolSignedH4(AH4 x){return ASatH4(x*AH4_(A_INFN_H));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// COLOR CONVERSIONS +//------------------------------------------------------------------------------------------------------------------------------ +// These are all linear to/from some other space (where 'linear' has been shortened out of the function name). +// So 'ToGamma' is 'LinearToGamma', and 'FromGamma' is 'LinearFromGamma'. +// These are branch free implementations. +// The AToSrgbF1() function is useful for stores for compute shaders for GPUs without hardware linear->sRGB store conversion. +//------------------------------------------------------------------------------------------------------------------------------ +// TRANSFER FUNCTIONS +// ================== +// 709 ..... Rec709 used for some HDTVs +// Gamma ... Typically 2.2 for some PC displays, or 2.4-2.5 for CRTs, or 2.2 FreeSync2 native +// Pq ...... PQ native for HDR10 +// Srgb .... The sRGB output, typical of PC displays, useful for 10-bit output, or storing to 8-bit UNORM without SRGB type +// Two ..... Gamma 2.0, fastest conversion (useful for intermediate pass approximations) +// Three ... Gamma 3.0, less fast, but good for HDR. +//------------------------------------------------------------------------------------------------------------------------------ +// KEEPING TO SPEC +// =============== +// Both Rec.709 and sRGB have a linear segment which as spec'ed would intersect the curved segment 2 times. +// (a.) For 8-bit sRGB, steps {0 to 10.3} are in the linear region (4% of the encoding range). +// (b.) For 8-bit 709, steps {0 to 20.7} are in the linear region (8% of the encoding range). +// Also there is a slight step in the transition regions. +// Precision of the coefficients in the spec being the likely cause. +// Main usage case of the sRGB code is to do the linear->sRGB converstion in a compute shader before store. +// This is to work around lack of hardware (typically only ROP does the conversion for free). +// To "correct" the linear segment, would be to introduce error, because hardware decode of sRGB->linear is fixed (and free). +// So this header keeps with the spec. +// For linear->sRGB transforms, the linear segment in some respects reduces error, because rounding in that region is linear. +// Rounding in the curved region in hardware (and fast software code) introduces error due to rounding in non-linear. +//------------------------------------------------------------------------------------------------------------------------------ +// FOR PQ +// ====== +// Both input and output is {0.0-1.0}, and where output 1.0 represents 10000.0 cd/m^2. +// All constants are only specified to FP32 precision. +// External PQ source reference, +// - https://github.com/ampas/aces-dev/blob/master/transforms/ctl/utilities/ACESlib.Utilities_Color.a1.0.1.ctl +//------------------------------------------------------------------------------------------------------------------------------ +// PACKED VERSIONS +// =============== +// These are the A*H2() functions. +// There is no PQ functions as FP16 seemed to not have enough precision for the conversion. +// The remaining functions are "good enough" for 8-bit, and maybe 10-bit if not concerned about a few 1-bit errors. +// Precision is lowest in the 709 conversion, higher in sRGB, higher still in Two and Gamma (when using 2.2 at least). +//------------------------------------------------------------------------------------------------------------------------------ +// NOTES +// ===== +// Could be faster for PQ conversions to be in ALU or a texture lookup depending on usage case. +//============================================================================================================================== + #if 1 + AF1 ATo709F1(AF1 c){AF3 j=AF3(0.018*4.5,4.5,0.45);AF2 k=AF2(1.099,-0.099); + return clamp(j.x ,c*j.y ,pow(c,j.z )*k.x +k.y );} + AF2 ATo709F2(AF2 c){AF3 j=AF3(0.018*4.5,4.5,0.45);AF2 k=AF2(1.099,-0.099); + return clamp(j.xx ,c*j.yy ,pow(c,j.zz )*k.xx +k.yy );} + AF3 ATo709F3(AF3 c){AF3 j=AF3(0.018*4.5,4.5,0.45);AF2 k=AF2(1.099,-0.099); + return clamp(j.xxx,c*j.yyy,pow(c,j.zzz)*k.xxx+k.yyy);} +//------------------------------------------------------------------------------------------------------------------------------ + // Note 'rcpX' is '1/x', where the 'x' is what would be used in AFromGamma(). + AF1 AToGammaF1(AF1 c,AF1 rcpX){return pow(c,AF1_(rcpX));} + AF2 AToGammaF2(AF2 c,AF1 rcpX){return pow(c,AF2_(rcpX));} + AF3 AToGammaF3(AF3 c,AF1 rcpX){return pow(c,AF3_(rcpX));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AToPqF1(AF1 x){AF1 p=pow(x,AF1_(0.159302)); + return pow((AF1_(0.835938)+AF1_(18.8516)*p)/(AF1_(1.0)+AF1_(18.6875)*p),AF1_(78.8438));} + AF2 AToPqF1(AF2 x){AF2 p=pow(x,AF2_(0.159302)); + return pow((AF2_(0.835938)+AF2_(18.8516)*p)/(AF2_(1.0)+AF2_(18.6875)*p),AF2_(78.8438));} + AF3 AToPqF1(AF3 x){AF3 p=pow(x,AF3_(0.159302)); + return pow((AF3_(0.835938)+AF3_(18.8516)*p)/(AF3_(1.0)+AF3_(18.6875)*p),AF3_(78.8438));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AToSrgbF1(AF1 c){AF3 j=AF3(0.0031308*12.92,12.92,1.0/2.4);AF2 k=AF2(1.055,-0.055); + return clamp(j.x ,c*j.y ,pow(c,j.z )*k.x +k.y );} + AF2 AToSrgbF2(AF2 c){AF3 j=AF3(0.0031308*12.92,12.92,1.0/2.4);AF2 k=AF2(1.055,-0.055); + return clamp(j.xx ,c*j.yy ,pow(c,j.zz )*k.xx +k.yy );} + AF3 AToSrgbF3(AF3 c){AF3 j=AF3(0.0031308*12.92,12.92,1.0/2.4);AF2 k=AF2(1.055,-0.055); + return clamp(j.xxx,c*j.yyy,pow(c,j.zzz)*k.xxx+k.yyy);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AToTwoF1(AF1 c){return sqrt(c);} + AF2 AToTwoF2(AF2 c){return sqrt(c);} + AF3 AToTwoF3(AF3 c){return sqrt(c);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AToThreeF1(AF1 c){return pow(c,AF1_(1.0/3.0));} + AF2 AToThreeF2(AF2 c){return pow(c,AF2_(1.0/3.0));} + AF3 AToThreeF3(AF3 c){return pow(c,AF3_(1.0/3.0));} + #endif +//============================================================================================================================== + #if 1 + // Unfortunately median won't work here. + AF1 AFrom709F1(AF1 c){AF3 j=AF3(0.081/4.5,1.0/4.5,1.0/0.45);AF2 k=AF2(1.0/1.099,0.099/1.099); + return AZolSelF1(AZolSignedF1(c-j.x ),c*j.y ,pow(c*k.x +k.y ,j.z ));} + AF2 AFrom709F2(AF2 c){AF3 j=AF3(0.081/4.5,1.0/4.5,1.0/0.45);AF2 k=AF2(1.0/1.099,0.099/1.099); + return AZolSelF2(AZolSignedF2(c-j.xx ),c*j.yy ,pow(c*k.xx +k.yy ,j.zz ));} + AF3 AFrom709F3(AF3 c){AF3 j=AF3(0.081/4.5,1.0/4.5,1.0/0.45);AF2 k=AF2(1.0/1.099,0.099/1.099); + return AZolSelF3(AZolSignedF3(c-j.xxx),c*j.yyy,pow(c*k.xxx+k.yyy,j.zzz));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AFromGammaF1(AF1 c,AF1 x){return pow(c,AF1_(x));} + AF2 AFromGammaF2(AF2 c,AF1 x){return pow(c,AF2_(x));} + AF3 AFromGammaF3(AF3 c,AF1 x){return pow(c,AF3_(x));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AFromPqF1(AF1 x){AF1 p=pow(x,AF1_(0.0126833)); + return pow(ASatF1(p-AF1_(0.835938))/(AF1_(18.8516)-AF1_(18.6875)*p),AF1_(6.27739));} + AF2 AFromPqF1(AF2 x){AF2 p=pow(x,AF2_(0.0126833)); + return pow(ASatF2(p-AF2_(0.835938))/(AF2_(18.8516)-AF2_(18.6875)*p),AF2_(6.27739));} + AF3 AFromPqF1(AF3 x){AF3 p=pow(x,AF3_(0.0126833)); + return pow(ASatF3(p-AF3_(0.835938))/(AF3_(18.8516)-AF3_(18.6875)*p),AF3_(6.27739));} +//------------------------------------------------------------------------------------------------------------------------------ + // Unfortunately median won't work here. + AF1 AFromSrgbF1(AF1 c){AF3 j=AF3(0.04045/12.92,1.0/12.92,2.4);AF2 k=AF2(1.0/1.055,0.055/1.055); + return AZolSelF1(AZolSignedF1(c-j.x ),c*j.y ,pow(c*k.x +k.y ,j.z ));} + AF2 AFromSrgbF2(AF2 c){AF3 j=AF3(0.04045/12.92,1.0/12.92,2.4);AF2 k=AF2(1.0/1.055,0.055/1.055); + return AZolSelF2(AZolSignedF2(c-j.xx ),c*j.yy ,pow(c*k.xx +k.yy ,j.zz ));} + AF3 AFromSrgbF3(AF3 c){AF3 j=AF3(0.04045/12.92,1.0/12.92,2.4);AF2 k=AF2(1.0/1.055,0.055/1.055); + return AZolSelF3(AZolSignedF3(c-j.xxx),c*j.yyy,pow(c*k.xxx+k.yyy,j.zzz));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AFromTwoF1(AF1 c){return c*c;} + AF2 AFromTwoF2(AF2 c){return c*c;} + AF3 AFromTwoF3(AF3 c){return c*c;} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AFromThreeF1(AF1 c){return c*c*c;} + AF2 AFromThreeF2(AF2 c){return c*c*c;} + AF3 AFromThreeF3(AF3 c){return c*c*c;} + #endif +//============================================================================================================================== + #ifdef A_HALF + AH1 ATo709H1(AH1 c){AH3 j=AH3(0.018*4.5,4.5,0.45);AH2 k=AH2(1.099,-0.099); + return clamp(j.x ,c*j.y ,pow(c,j.z )*k.x +k.y );} + AH2 ATo709H2(AH2 c){AH3 j=AH3(0.018*4.5,4.5,0.45);AH2 k=AH2(1.099,-0.099); + return clamp(j.xx ,c*j.yy ,pow(c,j.zz )*k.xx +k.yy );} + AH3 ATo709H3(AH3 c){AH3 j=AH3(0.018*4.5,4.5,0.45);AH2 k=AH2(1.099,-0.099); + return clamp(j.xxx,c*j.yyy,pow(c,j.zzz)*k.xxx+k.yyy);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AToGammaH1(AH1 c,AH1 rcpX){return pow(c,AH1_(rcpX));} + AH2 AToGammaH2(AH2 c,AH1 rcpX){return pow(c,AH2_(rcpX));} + AH3 AToGammaH3(AH3 c,AH1 rcpX){return pow(c,AH3_(rcpX));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AToSrgbH1(AH1 c){AH3 j=AH3(0.0031308*12.92,12.92,1.0/2.4);AH2 k=AH2(1.055,-0.055); + return clamp(j.x ,c*j.y ,pow(c,j.z )*k.x +k.y );} + AH2 AToSrgbH2(AH2 c){AH3 j=AH3(0.0031308*12.92,12.92,1.0/2.4);AH2 k=AH2(1.055,-0.055); + return clamp(j.xx ,c*j.yy ,pow(c,j.zz )*k.xx +k.yy );} + AH3 AToSrgbH3(AH3 c){AH3 j=AH3(0.0031308*12.92,12.92,1.0/2.4);AH2 k=AH2(1.055,-0.055); + return clamp(j.xxx,c*j.yyy,pow(c,j.zzz)*k.xxx+k.yyy);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AToTwoH1(AH1 c){return sqrt(c);} + AH2 AToTwoH2(AH2 c){return sqrt(c);} + AH3 AToTwoH3(AH3 c){return sqrt(c);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AToThreeH1(AH1 c){return pow(c,AH1_(1.0/3.0));} + AH2 AToThreeH2(AH2 c){return pow(c,AH2_(1.0/3.0));} + AH3 AToThreeH3(AH3 c){return pow(c,AH3_(1.0/3.0));} + #endif +//============================================================================================================================== + #ifdef A_HALF + AH1 AFrom709H1(AH1 c){AH3 j=AH3(0.081/4.5,1.0/4.5,1.0/0.45);AH2 k=AH2(1.0/1.099,0.099/1.099); + return AZolSelH1(AZolSignedH1(c-j.x ),c*j.y ,pow(c*k.x +k.y ,j.z ));} + AH2 AFrom709H2(AH2 c){AH3 j=AH3(0.081/4.5,1.0/4.5,1.0/0.45);AH2 k=AH2(1.0/1.099,0.099/1.099); + return AZolSelH2(AZolSignedH2(c-j.xx ),c*j.yy ,pow(c*k.xx +k.yy ,j.zz ));} + AH3 AFrom709H3(AH3 c){AH3 j=AH3(0.081/4.5,1.0/4.5,1.0/0.45);AH2 k=AH2(1.0/1.099,0.099/1.099); + return AZolSelH3(AZolSignedH3(c-j.xxx),c*j.yyy,pow(c*k.xxx+k.yyy,j.zzz));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AFromGammaH1(AH1 c,AH1 x){return pow(c,AH1_(x));} + AH2 AFromGammaH2(AH2 c,AH1 x){return pow(c,AH2_(x));} + AH3 AFromGammaH3(AH3 c,AH1 x){return pow(c,AH3_(x));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AHromSrgbF1(AH1 c){AH3 j=AH3(0.04045/12.92,1.0/12.92,2.4);AH2 k=AH2(1.0/1.055,0.055/1.055); + return AZolSelH1(AZolSignedH1(c-j.x ),c*j.y ,pow(c*k.x +k.y ,j.z ));} + AH2 AHromSrgbF2(AH2 c){AH3 j=AH3(0.04045/12.92,1.0/12.92,2.4);AH2 k=AH2(1.0/1.055,0.055/1.055); + return AZolSelH2(AZolSignedH2(c-j.xx ),c*j.yy ,pow(c*k.xx +k.yy ,j.zz ));} + AH3 AHromSrgbF3(AH3 c){AH3 j=AH3(0.04045/12.92,1.0/12.92,2.4);AH2 k=AH2(1.0/1.055,0.055/1.055); + return AZolSelH3(AZolSignedH3(c-j.xxx),c*j.yyy,pow(c*k.xxx+k.yyy,j.zzz));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AFromTwoH1(AH1 c){return c*c;} + AH2 AFromTwoH2(AH2 c){return c*c;} + AH3 AFromTwoH3(AH3 c){return c*c;} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AFromThreeH1(AH1 c){return c*c*c;} + AH2 AFromThreeH2(AH2 c){return c*c*c;} + AH3 AFromThreeH3(AH3 c){return c*c*c;} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// CS REMAP +//============================================================================================================================== + // Simple remap 64x1 to 8x8 with rotated 2x2 pixel quads in quad linear. + // 543210 + // ====== + // ..xxx. + // yy...y + AU2 ARmp8x8(AU1 a){return AU2(ABfe(a,1u,3u),ABfiM(ABfe(a,3u,3u),a,1u));} +//============================================================================================================================== + // More complex remap 64x1 to 8x8 which is necessary for 2D wave reductions. + // 543210 + // ====== + // .xx..x + // y..yy. + // Details, + // LANE TO 8x8 MAPPING + // =================== + // 00 01 08 09 10 11 18 19 + // 02 03 0a 0b 12 13 1a 1b + // 04 05 0c 0d 14 15 1c 1d + // 06 07 0e 0f 16 17 1e 1f + // 20 21 28 29 30 31 38 39 + // 22 23 2a 2b 32 33 3a 3b + // 24 25 2c 2d 34 35 3c 3d + // 26 27 2e 2f 36 37 3e 3f + AU2 ARmpRed8x8(AU1 a){return AU2(ABfiM(ABfe(a,2u,3u),a,1u),ABfiM(ABfe(a,3u,3u),ABfe(a,1u,2u),2u));} +//============================================================================================================================== + #ifdef A_HALF + AW2 ARmp8x8H(AU1 a){return AW2(ABfe(a,1u,3u),ABfiM(ABfe(a,3u,3u),a,1u));} + AW2 ARmpRed8x8H(AU1 a){return AW2(ABfiM(ABfe(a,2u,3u),a,1u),ABfiM(ABfe(a,3u,3u),ABfe(a,1u,2u),2u));} + #endif +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// REFERENCE +// +//------------------------------------------------------------------------------------------------------------------------------ +// IEEE FLOAT RULES +// ================ +// - saturate(NaN)=0, saturate(-INF)=0, saturate(+INF)=1 +// - {+/-}0 * {+/-}INF = NaN +// - -INF + (+INF) = NaN +// - {+/-}0 / {+/-}0 = NaN +// - {+/-}INF / {+/-}INF = NaN +// - a<(-0) := sqrt(a) = NaN (a=-0.0 won't NaN) +// - 0 == -0 +// - 4/0 = +INF +// - 4/-0 = -INF +// - 4+INF = +INF +// - 4-INF = -INF +// - 4*(+INF) = +INF +// - 4*(-INF) = -INF +// - -4*(+INF) = -INF +// - sqrt(+INF) = +INF +//------------------------------------------------------------------------------------------------------------------------------ +// FP16 ENCODING +// ============= +// fedcba9876543210 +// ---------------- +// ......mmmmmmmmmm 10-bit mantissa (encodes 11-bit 0.5 to 1.0 except for denormals) +// .eeeee.......... 5-bit exponent +// .00000.......... denormals +// .00001.......... -14 exponent +// .11110.......... 15 exponent +// .111110000000000 infinity +// .11111nnnnnnnnnn NaN with n!=0 +// s............... sign +//------------------------------------------------------------------------------------------------------------------------------ +// FP16/INT16 ALIASING DENORMAL +// ============================ +// 11-bit unsigned integers alias with half float denormal/normal values, +// 1 = 2^(-24) = 1/16777216 ....................... first denormal value +// 2 = 2^(-23) +// ... +// 1023 = 2^(-14)*(1-2^(-10)) = 2^(-14)*(1-1/1024) ... last denormal value +// 1024 = 2^(-14) = 1/16384 .......................... first normal value that still maps to integers +// 2047 .............................................. last normal value that still maps to integers +// Scaling limits, +// 2^15 = 32768 ...................................... largest power of 2 scaling +// Largest pow2 conversion mapping is at *32768, +// 1 : 2^(-9) = 1/512 +// 2 : 1/256 +// 4 : 1/128 +// 8 : 1/64 +// 16 : 1/32 +// 32 : 1/16 +// 64 : 1/8 +// 128 : 1/4 +// 256 : 1/2 +// 512 : 1 +// 1024 : 2 +// 2047 : a little less than 4 +//============================================================================================================================== +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// GPU/CPU PORTABILITY +// +// +//------------------------------------------------------------------------------------------------------------------------------ +// This is the GPU implementation. +// See the CPU implementation for docs. +//============================================================================================================================== +#ifdef A_GPU + #define A_TRUE true + #define A_FALSE false + #define A_STATIC +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// VECTOR ARGUMENT/RETURN/INITIALIZATION PORTABILITY +//============================================================================================================================== + #define retAD2 AD2 + #define retAD3 AD3 + #define retAD4 AD4 + #define retAF2 AF2 + #define retAF3 AF3 + #define retAF4 AF4 + #define retAL2 AL2 + #define retAL3 AL3 + #define retAL4 AL4 + #define retAU2 AU2 + #define retAU3 AU3 + #define retAU4 AU4 +//------------------------------------------------------------------------------------------------------------------------------ + #define inAD2 in AD2 + #define inAD3 in AD3 + #define inAD4 in AD4 + #define inAF2 in AF2 + #define inAF3 in AF3 + #define inAF4 in AF4 + #define inAL2 in AL2 + #define inAL3 in AL3 + #define inAL4 in AL4 + #define inAU2 in AU2 + #define inAU3 in AU3 + #define inAU4 in AU4 +//------------------------------------------------------------------------------------------------------------------------------ + #define inoutAD2 inout AD2 + #define inoutAD3 inout AD3 + #define inoutAD4 inout AD4 + #define inoutAF2 inout AF2 + #define inoutAF3 inout AF3 + #define inoutAF4 inout AF4 + #define inoutAL2 inout AL2 + #define inoutAL3 inout AL3 + #define inoutAL4 inout AL4 + #define inoutAU2 inout AU2 + #define inoutAU3 inout AU3 + #define inoutAU4 inout AU4 +//------------------------------------------------------------------------------------------------------------------------------ + #define outAD2 out AD2 + #define outAD3 out AD3 + #define outAD4 out AD4 + #define outAF2 out AF2 + #define outAF3 out AF3 + #define outAF4 out AF4 + #define outAL2 out AL2 + #define outAL3 out AL3 + #define outAL4 out AL4 + #define outAU2 out AU2 + #define outAU3 out AU3 + #define outAU4 out AU4 +//------------------------------------------------------------------------------------------------------------------------------ + #define varAD2(x) AD2 x + #define varAD3(x) AD3 x + #define varAD4(x) AD4 x + #define varAF2(x) AF2 x + #define varAF3(x) AF3 x + #define varAF4(x) AF4 x + #define varAL2(x) AL2 x + #define varAL3(x) AL3 x + #define varAL4(x) AL4 x + #define varAU2(x) AU2 x + #define varAU3(x) AU3 x + #define varAU4(x) AU4 x +//------------------------------------------------------------------------------------------------------------------------------ + #define initAD2(x,y) AD2(x,y) + #define initAD3(x,y,z) AD3(x,y,z) + #define initAD4(x,y,z,w) AD4(x,y,z,w) + #define initAF2(x,y) AF2(x,y) + #define initAF3(x,y,z) AF3(x,y,z) + #define initAF4(x,y,z,w) AF4(x,y,z,w) + #define initAL2(x,y) AL2(x,y) + #define initAL3(x,y,z) AL3(x,y,z) + #define initAL4(x,y,z,w) AL4(x,y,z,w) + #define initAU2(x,y) AU2(x,y) + #define initAU3(x,y,z) AU3(x,y,z) + #define initAU4(x,y,z,w) AU4(x,y,z,w) +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// SCALAR RETURN OPS +//============================================================================================================================== + #define AAbsD1(a) abs(AD1(a)) + #define AAbsF1(a) abs(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define ACosD1(a) cos(AD1(a)) + #define ACosF1(a) cos(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define ADotD2(a,b) dot(AD2(a),AD2(b)) + #define ADotD3(a,b) dot(AD3(a),AD3(b)) + #define ADotD4(a,b) dot(AD4(a),AD4(b)) + #define ADotF2(a,b) dot(AF2(a),AF2(b)) + #define ADotF3(a,b) dot(AF3(a),AF3(b)) + #define ADotF4(a,b) dot(AF4(a),AF4(b)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AExp2D1(a) exp2(AD1(a)) + #define AExp2F1(a) exp2(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AFloorD1(a) floor(AD1(a)) + #define AFloorF1(a) floor(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define ALog2D1(a) log2(AD1(a)) + #define ALog2F1(a) log2(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AMaxD1(a,b) max(a,b) + #define AMaxF1(a,b) max(a,b) + #define AMaxL1(a,b) max(a,b) + #define AMaxU1(a,b) max(a,b) +//------------------------------------------------------------------------------------------------------------------------------ + #define AMinD1(a,b) min(a,b) + #define AMinF1(a,b) min(a,b) + #define AMinL1(a,b) min(a,b) + #define AMinU1(a,b) min(a,b) +//------------------------------------------------------------------------------------------------------------------------------ + #define ASinD1(a) sin(AD1(a)) + #define ASinF1(a) sin(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define ASqrtD1(a) sqrt(AD1(a)) + #define ASqrtF1(a) sqrt(AF1(a)) +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// SCALAR RETURN OPS - DEPENDENT +//============================================================================================================================== + #define APowD1(a,b) pow(AD1(a),AF1(b)) + #define APowF1(a,b) pow(AF1(a),AF1(b)) +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// VECTOR OPS +//------------------------------------------------------------------------------------------------------------------------------ +// These are added as needed for production or prototyping, so not necessarily a complete set. +// They follow a convention of taking in a destination and also returning the destination value to increase utility. +//============================================================================================================================== + #ifdef A_DUBL + AD2 opAAbsD2(outAD2 d,inAD2 a){d=abs(a);return d;} + AD3 opAAbsD3(outAD3 d,inAD3 a){d=abs(a);return d;} + AD4 opAAbsD4(outAD4 d,inAD4 a){d=abs(a);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAAddD2(outAD2 d,inAD2 a,inAD2 b){d=a+b;return d;} + AD3 opAAddD3(outAD3 d,inAD3 a,inAD3 b){d=a+b;return d;} + AD4 opAAddD4(outAD4 d,inAD4 a,inAD4 b){d=a+b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAAddOneD2(outAD2 d,inAD2 a,AD1 b){d=a+AD2_(b);return d;} + AD3 opAAddOneD3(outAD3 d,inAD3 a,AD1 b){d=a+AD3_(b);return d;} + AD4 opAAddOneD4(outAD4 d,inAD4 a,AD1 b){d=a+AD4_(b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opACpyD2(outAD2 d,inAD2 a){d=a;return d;} + AD3 opACpyD3(outAD3 d,inAD3 a){d=a;return d;} + AD4 opACpyD4(outAD4 d,inAD4 a){d=a;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opALerpD2(outAD2 d,inAD2 a,inAD2 b,inAD2 c){d=ALerpD2(a,b,c);return d;} + AD3 opALerpD3(outAD3 d,inAD3 a,inAD3 b,inAD3 c){d=ALerpD3(a,b,c);return d;} + AD4 opALerpD4(outAD4 d,inAD4 a,inAD4 b,inAD4 c){d=ALerpD4(a,b,c);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opALerpOneD2(outAD2 d,inAD2 a,inAD2 b,AD1 c){d=ALerpD2(a,b,AD2_(c));return d;} + AD3 opALerpOneD3(outAD3 d,inAD3 a,inAD3 b,AD1 c){d=ALerpD3(a,b,AD3_(c));return d;} + AD4 opALerpOneD4(outAD4 d,inAD4 a,inAD4 b,AD1 c){d=ALerpD4(a,b,AD4_(c));return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAMaxD2(outAD2 d,inAD2 a,inAD2 b){d=max(a,b);return d;} + AD3 opAMaxD3(outAD3 d,inAD3 a,inAD3 b){d=max(a,b);return d;} + AD4 opAMaxD4(outAD4 d,inAD4 a,inAD4 b){d=max(a,b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAMinD2(outAD2 d,inAD2 a,inAD2 b){d=min(a,b);return d;} + AD3 opAMinD3(outAD3 d,inAD3 a,inAD3 b){d=min(a,b);return d;} + AD4 opAMinD4(outAD4 d,inAD4 a,inAD4 b){d=min(a,b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAMulD2(outAD2 d,inAD2 a,inAD2 b){d=a*b;return d;} + AD3 opAMulD3(outAD3 d,inAD3 a,inAD3 b){d=a*b;return d;} + AD4 opAMulD4(outAD4 d,inAD4 a,inAD4 b){d=a*b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAMulOneD2(outAD2 d,inAD2 a,AD1 b){d=a*AD2_(b);return d;} + AD3 opAMulOneD3(outAD3 d,inAD3 a,AD1 b){d=a*AD3_(b);return d;} + AD4 opAMulOneD4(outAD4 d,inAD4 a,AD1 b){d=a*AD4_(b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opANegD2(outAD2 d,inAD2 a){d=-a;return d;} + AD3 opANegD3(outAD3 d,inAD3 a){d=-a;return d;} + AD4 opANegD4(outAD4 d,inAD4 a){d=-a;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opARcpD2(outAD2 d,inAD2 a){d=ARcpD2(a);return d;} + AD3 opARcpD3(outAD3 d,inAD3 a){d=ARcpD3(a);return d;} + AD4 opARcpD4(outAD4 d,inAD4 a){d=ARcpD4(a);return d;} + #endif +//============================================================================================================================== + AF2 opAAbsF2(outAF2 d,inAF2 a){d=abs(a);return d;} + AF3 opAAbsF3(outAF3 d,inAF3 a){d=abs(a);return d;} + AF4 opAAbsF4(outAF4 d,inAF4 a){d=abs(a);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAAddF2(outAF2 d,inAF2 a,inAF2 b){d=a+b;return d;} + AF3 opAAddF3(outAF3 d,inAF3 a,inAF3 b){d=a+b;return d;} + AF4 opAAddF4(outAF4 d,inAF4 a,inAF4 b){d=a+b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAAddOneF2(outAF2 d,inAF2 a,AF1 b){d=a+AF2_(b);return d;} + AF3 opAAddOneF3(outAF3 d,inAF3 a,AF1 b){d=a+AF3_(b);return d;} + AF4 opAAddOneF4(outAF4 d,inAF4 a,AF1 b){d=a+AF4_(b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opACpyF2(outAF2 d,inAF2 a){d=a;return d;} + AF3 opACpyF3(outAF3 d,inAF3 a){d=a;return d;} + AF4 opACpyF4(outAF4 d,inAF4 a){d=a;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opALerpF2(outAF2 d,inAF2 a,inAF2 b,inAF2 c){d=ALerpF2(a,b,c);return d;} + AF3 opALerpF3(outAF3 d,inAF3 a,inAF3 b,inAF3 c){d=ALerpF3(a,b,c);return d;} + AF4 opALerpF4(outAF4 d,inAF4 a,inAF4 b,inAF4 c){d=ALerpF4(a,b,c);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opALerpOneF2(outAF2 d,inAF2 a,inAF2 b,AF1 c){d=ALerpF2(a,b,AF2_(c));return d;} + AF3 opALerpOneF3(outAF3 d,inAF3 a,inAF3 b,AF1 c){d=ALerpF3(a,b,AF3_(c));return d;} + AF4 opALerpOneF4(outAF4 d,inAF4 a,inAF4 b,AF1 c){d=ALerpF4(a,b,AF4_(c));return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAMaxF2(outAF2 d,inAF2 a,inAF2 b){d=max(a,b);return d;} + AF3 opAMaxF3(outAF3 d,inAF3 a,inAF3 b){d=max(a,b);return d;} + AF4 opAMaxF4(outAF4 d,inAF4 a,inAF4 b){d=max(a,b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAMinF2(outAF2 d,inAF2 a,inAF2 b){d=min(a,b);return d;} + AF3 opAMinF3(outAF3 d,inAF3 a,inAF3 b){d=min(a,b);return d;} + AF4 opAMinF4(outAF4 d,inAF4 a,inAF4 b){d=min(a,b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAMulF2(outAF2 d,inAF2 a,inAF2 b){d=a*b;return d;} + AF3 opAMulF3(outAF3 d,inAF3 a,inAF3 b){d=a*b;return d;} + AF4 opAMulF4(outAF4 d,inAF4 a,inAF4 b){d=a*b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAMulOneF2(outAF2 d,inAF2 a,AF1 b){d=a*AF2_(b);return d;} + AF3 opAMulOneF3(outAF3 d,inAF3 a,AF1 b){d=a*AF3_(b);return d;} + AF4 opAMulOneF4(outAF4 d,inAF4 a,AF1 b){d=a*AF4_(b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opANegF2(outAF2 d,inAF2 a){d=-a;return d;} + AF3 opANegF3(outAF3 d,inAF3 a){d=-a;return d;} + AF4 opANegF4(outAF4 d,inAF4 a){d=-a;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opARcpF2(outAF2 d,inAF2 a){d=ARcpF2(a);return d;} + AF3 opARcpF3(outAF3 d,inAF3 a){d=ARcpF3(a);return d;} + AF4 opARcpF4(outAF4 d,inAF4 a){d=ARcpF4(a);return d;} +#endif diff --git a/examples/46-fsr/ffx_fsr1.h b/examples/46-fsr/ffx_fsr1.h new file mode 100644 index 000000000..15ecfde5c --- /dev/null +++ b/examples/46-fsr/ffx_fsr1.h @@ -0,0 +1,1199 @@ +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// AMD FidelityFX SUPER RESOLUTION [FSR 1] ::: SPATIAL SCALING & EXTRAS - v1.20210629 +// +// +//------------------------------------------------------------------------------------------------------------------------------ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//------------------------------------------------------------------------------------------------------------------------------ +// FidelityFX Super Resolution Sample +// +// Copyright (c) 2021 Advanced Micro Devices, Inc. All rights reserved. +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//------------------------------------------------------------------------------------------------------------------------------ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//------------------------------------------------------------------------------------------------------------------------------ +// ABOUT +// ===== +// FSR is a collection of algorithms relating to generating a higher resolution image. +// This specific header focuses on single-image non-temporal image scaling, and related tools. +// +// The core functions are EASU and RCAS: +// [EASU] Edge Adaptive Spatial Upsampling ....... 1x to 4x area range spatial scaling, clamped adaptive elliptical filter. +// [RCAS] Robust Contrast Adaptive Sharpening .... A non-scaling variation on CAS. +// RCAS needs to be applied after EASU as a separate pass. +// +// Optional utility functions are: +// [LFGA] Linear Film Grain Applicator ........... Tool to apply film grain after scaling. +// [SRTM] Simple Reversible Tone-Mapper .......... Linear HDR {0 to FP16_MAX} to {0 to 1} and back. +// [TEPD] Temporal Energy Preserving Dither ...... Temporally energy preserving dithered {0 to 1} linear to gamma 2.0 conversion. +// See each individual sub-section for inline documentation. +//------------------------------------------------------------------------------------------------------------------------------ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//------------------------------------------------------------------------------------------------------------------------------ +// FUNCTION PERMUTATIONS +// ===================== +// *F() ..... Single item computation with 32-bit. +// *H() ..... Single item computation with 16-bit, with packing (aka two 16-bit ops in parallel) when possible. +// *Hx2() ... Processing two items in parallel with 16-bit, easier packing. +// Not all interfaces in this file have a *Hx2() form. +//============================================================================================================================== +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// FSR - [EASU] EDGE ADAPTIVE SPATIAL UPSAMPLING +// +//------------------------------------------------------------------------------------------------------------------------------ +// EASU provides a high quality spatial-only scaling at relatively low cost. +// Meaning EASU is appropiate for laptops and other low-end GPUs. +// Quality from 1x to 4x area scaling is good. +//------------------------------------------------------------------------------------------------------------------------------ +// The scalar uses a modified fast approximation to the standard lanczos(size=2) kernel. +// EASU runs in a single pass, so it applies a directionally and anisotropically adaptive radial lanczos. +// This is also kept as simple as possible to have minimum runtime. +//------------------------------------------------------------------------------------------------------------------------------ +// The lanzcos filter has negative lobes, so by itself it will introduce ringing. +// To remove all ringing, the algorithm uses the nearest 2x2 input texels as a neighborhood, +// and limits output to the minimum and maximum of that neighborhood. +//------------------------------------------------------------------------------------------------------------------------------ +// Input image requirements: +// +// Color needs to be encoded as 3 channel[red, green, blue](e.g.XYZ not supported) +// Each channel needs to be in the range[0, 1] +// Any color primaries are supported +// Display / tonemapping curve needs to be as if presenting to sRGB display or similar(e.g.Gamma 2.0) +// There should be no banding in the input +// There should be no high amplitude noise in the input +// There should be no noise in the input that is not at input pixel granularity +// For performance purposes, use 32bpp formats +//------------------------------------------------------------------------------------------------------------------------------ +// Best to apply EASU at the end of the frame after tonemapping +// but before film grain or composite of the UI. +//------------------------------------------------------------------------------------------------------------------------------ +// Example of including this header for D3D HLSL : +// +// #define A_GPU 1 +// #define A_HLSL 1 +// #define A_HALF 1 +// #include "ffx_a.h" +// #define FSR_EASU_H 1 +// #define FSR_RCAS_H 1 +// //declare input callbacks +// #include "ffx_fsr1.h" +// +// Example of including this header for Vulkan GLSL : +// +// #define A_GPU 1 +// #define A_GLSL 1 +// #define A_HALF 1 +// #include "ffx_a.h" +// #define FSR_EASU_H 1 +// #define FSR_RCAS_H 1 +// //declare input callbacks +// #include "ffx_fsr1.h" +// +// Example of including this header for Vulkan HLSL : +// +// #define A_GPU 1 +// #define A_HLSL 1 +// #define A_HLSL_6_2 1 +// #define A_NO_16_BIT_CAST 1 +// #define A_HALF 1 +// #include "ffx_a.h" +// #define FSR_EASU_H 1 +// #define FSR_RCAS_H 1 +// //declare input callbacks +// #include "ffx_fsr1.h" +// +// Example of declaring the required input callbacks for GLSL : +// The callbacks need to gather4 for each color channel using the specified texture coordinate 'p'. +// EASU uses gather4 to reduce position computation logic and for free Arrays of Structures to Structures of Arrays conversion. +// +// AH4 FsrEasuRH(AF2 p){return AH4(textureGather(sampler2D(tex,sam),p,0));} +// AH4 FsrEasuGH(AF2 p){return AH4(textureGather(sampler2D(tex,sam),p,1));} +// AH4 FsrEasuBH(AF2 p){return AH4(textureGather(sampler2D(tex,sam),p,2));} +// ... +// The FsrEasuCon function needs to be called from the CPU or GPU to set up constants. +// The difference in viewport and input image size is there to support Dynamic Resolution Scaling. +// To use FsrEasuCon() on the CPU, define A_CPU before including ffx_a and ffx_fsr1. +// Including a GPU example here, the 'con0' through 'con3' values would be stored out to a constant buffer. +// AU4 con0,con1,con2,con3; +// FsrEasuCon(con0,con1,con2,con3, +// 1920.0,1080.0, // Viewport size (top left aligned) in the input image which is to be scaled. +// 3840.0,2160.0, // The size of the input image. +// 2560.0,1440.0); // The output resolution. +//============================================================================================================================== +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// CONSTANT SETUP +//============================================================================================================================== +// Call to setup required constant values (works on CPU or GPU). +A_STATIC void FsrEasuCon( +outAU4 con0, +outAU4 con1, +outAU4 con2, +outAU4 con3, +// This the rendered image resolution being upscaled +AF1 inputViewportInPixelsX, +AF1 inputViewportInPixelsY, +// This is the resolution of the resource containing the input image (useful for dynamic resolution) +AF1 inputSizeInPixelsX, +AF1 inputSizeInPixelsY, +// This is the display resolution which the input image gets upscaled to +AF1 outputSizeInPixelsX, +AF1 outputSizeInPixelsY){ + // Output integer position to a pixel position in viewport. + con0[0]=AU1_AF1(inputViewportInPixelsX*ARcpF1(outputSizeInPixelsX)); + con0[1]=AU1_AF1(inputViewportInPixelsY*ARcpF1(outputSizeInPixelsY)); + con0[2]=AU1_AF1(AF1_(0.5)*inputViewportInPixelsX*ARcpF1(outputSizeInPixelsX)-AF1_(0.5)); + con0[3]=AU1_AF1(AF1_(0.5)*inputViewportInPixelsY*ARcpF1(outputSizeInPixelsY)-AF1_(0.5)); + // Viewport pixel position to normalized image space. + // This is used to get upper-left of 'F' tap. + con1[0]=AU1_AF1(ARcpF1(inputSizeInPixelsX)); + con1[1]=AU1_AF1(ARcpF1(inputSizeInPixelsY)); + // Centers of gather4, first offset from upper-left of 'F'. + // +---+---+ + // | | | + // +--(0)--+ + // | b | c | + // +---F---+---+---+ + // | e | f | g | h | + // +--(1)--+--(2)--+ + // | i | j | k | l | + // +---+---+---+---+ + // | n | o | + // +--(3)--+ + // | | | + // +---+---+ + con1[2]=AU1_AF1(AF1_( 1.0)*ARcpF1(inputSizeInPixelsX)); + con1[3]=AU1_AF1(AF1_(-1.0)*ARcpF1(inputSizeInPixelsY)); + // These are from (0) instead of 'F'. + con2[0]=AU1_AF1(AF1_(-1.0)*ARcpF1(inputSizeInPixelsX)); + con2[1]=AU1_AF1(AF1_( 2.0)*ARcpF1(inputSizeInPixelsY)); + con2[2]=AU1_AF1(AF1_( 1.0)*ARcpF1(inputSizeInPixelsX)); + con2[3]=AU1_AF1(AF1_( 2.0)*ARcpF1(inputSizeInPixelsY)); + con3[0]=AU1_AF1(AF1_( 0.0)*ARcpF1(inputSizeInPixelsX)); + con3[1]=AU1_AF1(AF1_( 4.0)*ARcpF1(inputSizeInPixelsY)); + con3[2]=con3[3]=0;} + +//If the an offset into the input image resource +A_STATIC void FsrEasuConOffset( + outAU4 con0, + outAU4 con1, + outAU4 con2, + outAU4 con3, + // This the rendered image resolution being upscaled + AF1 inputViewportInPixelsX, + AF1 inputViewportInPixelsY, + // This is the resolution of the resource containing the input image (useful for dynamic resolution) + AF1 inputSizeInPixelsX, + AF1 inputSizeInPixelsY, + // This is the display resolution which the input image gets upscaled to + AF1 outputSizeInPixelsX, + AF1 outputSizeInPixelsY, + // This is the input image offset into the resource containing it (useful for dynamic resolution) + AF1 inputOffsetInPixelsX, + AF1 inputOffsetInPixelsY) { + FsrEasuCon(con0, con1, con2, con3, inputViewportInPixelsX, inputViewportInPixelsY, inputSizeInPixelsX, inputSizeInPixelsY, outputSizeInPixelsX, outputSizeInPixelsY); + con0[2] = AU1_AF1(AF1_(0.5) * inputViewportInPixelsX * ARcpF1(outputSizeInPixelsX) - AF1_(0.5) + inputOffsetInPixelsX); + con0[3] = AU1_AF1(AF1_(0.5) * inputViewportInPixelsY * ARcpF1(outputSizeInPixelsY) - AF1_(0.5) + inputOffsetInPixelsY); +} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// NON-PACKED 32-BIT VERSION +//============================================================================================================================== +#if defined(A_GPU)&&defined(FSR_EASU_F) + // Input callback prototypes, need to be implemented by calling shader + AF4 FsrEasuRF(AF2 p); + AF4 FsrEasuGF(AF2 p); + AF4 FsrEasuBF(AF2 p); +//------------------------------------------------------------------------------------------------------------------------------ + // Filtering for a given tap for the scalar. + void FsrEasuTapF( + inout AF3 aC, // Accumulated color, with negative lobe. + inout AF1 aW, // Accumulated weight. + AF2 off, // Pixel offset from resolve position to tap. + AF2 dir, // Gradient direction. + AF2 len, // Length. + AF1 lob, // Negative lobe strength. + AF1 clp, // Clipping point. + AF3 c){ // Tap color. + // Rotate offset by direction. + AF2 v; + v.x=(off.x*( dir.x))+(off.y*dir.y); + v.y=(off.x*(-dir.y))+(off.y*dir.x); + // Anisotropy. + v*=len; + // Compute distance^2. + AF1 d2=v.x*v.x+v.y*v.y; + // Limit to the window as at corner, 2 taps can easily be outside. + d2=min(d2,clp); + // Approximation of lancos2 without sin() or rcp(), or sqrt() to get x. + // (25/16 * (2/5 * x^2 - 1)^2 - (25/16 - 1)) * (1/4 * x^2 - 1)^2 + // |_______________________________________| |_______________| + // base window + // The general form of the 'base' is, + // (a*(b*x^2-1)^2-(a-1)) + // Where 'a=1/(2*b-b^2)' and 'b' moves around the negative lobe. + AF1 wB=AF1_(2.0/5.0)*d2+AF1_(-1.0); + AF1 wA=lob*d2+AF1_(-1.0); + wB*=wB; + wA*=wA; + wB=AF1_(25.0/16.0)*wB+AF1_(-(25.0/16.0-1.0)); + AF1 w=wB*wA; + // Do weighted average. + aC+=c*w;aW+=w;} +//------------------------------------------------------------------------------------------------------------------------------ + // Accumulate direction and length. + void FsrEasuSetF( + inout AF2 dir, + inout AF1 len, + AF2 pp, + AP1 biS,AP1 biT,AP1 biU,AP1 biV, + AF1 lA,AF1 lB,AF1 lC,AF1 lD,AF1 lE){ + // Compute bilinear weight, branches factor out as predicates are compiler time immediates. + // s t + // u v + AF1 w = AF1_(0.0); + if(biS)w=(AF1_(1.0)-pp.x)*(AF1_(1.0)-pp.y); + if(biT)w= pp.x *(AF1_(1.0)-pp.y); + if(biU)w=(AF1_(1.0)-pp.x)* pp.y ; + if(biV)w= pp.x * pp.y ; + // Direction is the '+' diff. + // a + // b c d + // e + // Then takes magnitude from abs average of both sides of 'c'. + // Length converts gradient reversal to 0, smoothly to non-reversal at 1, shaped, then adding horz and vert terms. + AF1 dc=lD-lC; + AF1 cb=lC-lB; + AF1 lenX=max(abs(dc),abs(cb)); + lenX=APrxLoRcpF1(lenX); + AF1 dirX=lD-lB; + dir.x+=dirX*w; + lenX=ASatF1(abs(dirX)*lenX); + lenX*=lenX; + len+=lenX*w; + // Repeat for the y axis. + AF1 ec=lE-lC; + AF1 ca=lC-lA; + AF1 lenY=max(abs(ec),abs(ca)); + lenY=APrxLoRcpF1(lenY); + AF1 dirY=lE-lA; + dir.y+=dirY*w; + lenY=ASatF1(abs(dirY)*lenY); + lenY*=lenY; + len+=lenY*w;} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrEasuF( + out AF3 pix, + AU2 ip, // Integer pixel position in output. + AU4 con0, // Constants generated by FsrEasuCon(). + AU4 con1, + AU4 con2, + AU4 con3){ +//------------------------------------------------------------------------------------------------------------------------------ + // Get position of 'f'. + AF2 pp=AF2(ip)*AF2_AU2(con0.xy)+AF2_AU2(con0.zw); + AF2 fp=floor(pp); + pp-=fp; +//------------------------------------------------------------------------------------------------------------------------------ + // 12-tap kernel. + // b c + // e f g h + // i j k l + // n o + // Gather 4 ordering. + // a b + // r g + // For packed FP16, need either {rg} or {ab} so using the following setup for gather in all versions, + // a b <- unused (z) + // r g + // a b a b + // r g r g + // a b + // r g <- unused (z) + // Allowing dead-code removal to remove the 'z's. + AF2 p0=fp*AF2_AU2(con1.xy)+AF2_AU2(con1.zw); + // These are from p0 to avoid pulling two constants on pre-Navi hardware. + AF2 p1=p0+AF2_AU2(con2.xy); + AF2 p2=p0+AF2_AU2(con2.zw); + AF2 p3=p0+AF2_AU2(con3.xy); + AF4 bczzR=FsrEasuRF(p0); + AF4 bczzG=FsrEasuGF(p0); + AF4 bczzB=FsrEasuBF(p0); + AF4 ijfeR=FsrEasuRF(p1); + AF4 ijfeG=FsrEasuGF(p1); + AF4 ijfeB=FsrEasuBF(p1); + AF4 klhgR=FsrEasuRF(p2); + AF4 klhgG=FsrEasuGF(p2); + AF4 klhgB=FsrEasuBF(p2); + AF4 zzonR=FsrEasuRF(p3); + AF4 zzonG=FsrEasuGF(p3); + AF4 zzonB=FsrEasuBF(p3); +//------------------------------------------------------------------------------------------------------------------------------ + // Simplest multi-channel approximate luma possible (luma times 2, in 2 FMA/MAD). + AF4 bczzL=bczzB*AF4_(0.5)+(bczzR*AF4_(0.5)+bczzG); + AF4 ijfeL=ijfeB*AF4_(0.5)+(ijfeR*AF4_(0.5)+ijfeG); + AF4 klhgL=klhgB*AF4_(0.5)+(klhgR*AF4_(0.5)+klhgG); + AF4 zzonL=zzonB*AF4_(0.5)+(zzonR*AF4_(0.5)+zzonG); + // Rename. + AF1 bL=bczzL.x; + AF1 cL=bczzL.y; + AF1 iL=ijfeL.x; + AF1 jL=ijfeL.y; + AF1 fL=ijfeL.z; + AF1 eL=ijfeL.w; + AF1 kL=klhgL.x; + AF1 lL=klhgL.y; + AF1 hL=klhgL.z; + AF1 gL=klhgL.w; + AF1 oL=zzonL.z; + AF1 nL=zzonL.w; + // Accumulate for bilinear interpolation. + AF2 dir=AF2_(0.0); + AF1 len=AF1_(0.0); + FsrEasuSetF(dir,len,pp,true, false,false,false,bL,eL,fL,gL,jL); + FsrEasuSetF(dir,len,pp,false,true ,false,false,cL,fL,gL,hL,kL); + FsrEasuSetF(dir,len,pp,false,false,true ,false,fL,iL,jL,kL,nL); + FsrEasuSetF(dir,len,pp,false,false,false,true ,gL,jL,kL,lL,oL); +//------------------------------------------------------------------------------------------------------------------------------ + // Normalize with approximation, and cleanup close to zero. + AF2 dir2=dir*dir; + AF1 dirR=dir2.x+dir2.y; + AP1 zro=dirR w = -m/(n+e+w+s) +// 1 == (w*(n+e+w+s)+m)/(4*w+1) -> w = (1-m)/(n+e+w+s-4*1) +// Then chooses the 'w' which results in no clipping, limits 'w', and multiplies by the 'sharp' amount. +// This solution above has issues with MSAA input as the steps along the gradient cause edge detection issues. +// So RCAS uses 4x the maximum and 4x the minimum (depending on equation)in place of the individual taps. +// As well as switching from 'm' to either the minimum or maximum (depending on side), to help in energy conservation. +// This stabilizes RCAS. +// RCAS does a simple highpass which is normalized against the local contrast then shaped, +// 0.25 +// 0.25 -1 0.25 +// 0.25 +// This is used as a noise detection filter, to reduce the effect of RCAS on grain, and focus on real edges. +// +// GLSL example for the required callbacks : +// +// AH4 FsrRcasLoadH(ASW2 p){return AH4(imageLoad(imgSrc,ASU2(p)));} +// void FsrRcasInputH(inout AH1 r,inout AH1 g,inout AH1 b) +// { +// //do any simple input color conversions here or leave empty if none needed +// } +// +// FsrRcasCon need to be called from the CPU or GPU to set up constants. +// Including a GPU example here, the 'con' value would be stored out to a constant buffer. +// +// AU4 con; +// FsrRcasCon(con, +// 0.0); // The scale is {0.0 := maximum sharpness, to N>0, where N is the number of stops (halving) of the reduction of sharpness}. +// --------------- +// RCAS sharpening supports a CAS-like pass-through alpha via, +// #define FSR_RCAS_PASSTHROUGH_ALPHA 1 +// RCAS also supports a define to enable a more expensive path to avoid some sharpening of noise. +// Would suggest it is better to apply film grain after RCAS sharpening (and after scaling) instead of using this define, +// #define FSR_RCAS_DENOISE 1 +//============================================================================================================================== +// This is set at the limit of providing unnatural results for sharpening. +#define FSR_RCAS_LIMIT (0.25-(1.0/16.0)) +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// CONSTANT SETUP +//============================================================================================================================== +// Call to setup required constant values (works on CPU or GPU). +A_STATIC void FsrRcasCon( +outAU4 con, +// The scale is {0.0 := maximum, to N>0, where N is the number of stops (halving) of the reduction of sharpness}. +AF1 sharpness){ + // Transform from stops to linear value. + sharpness=AExp2F1(-sharpness); + varAF2(hSharp)=initAF2(sharpness,sharpness); + con[0]=AU1_AF1(sharpness); + con[1]=AU1_AH2_AF2(hSharp); + con[2]=0; + con[3]=0;} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// NON-PACKED 32-BIT VERSION +//============================================================================================================================== +#if defined(A_GPU)&&defined(FSR_RCAS_F) + // Input callback prototypes that need to be implemented by calling shader + AF4 FsrRcasLoadF(ASU2 p); + void FsrRcasInputF(inout AF1 r,inout AF1 g,inout AF1 b); +//------------------------------------------------------------------------------------------------------------------------------ + void FsrRcasF( + out AF1 pixR, // Output values, non-vector so port between RcasFilter() and RcasFilterH() is easy. + out AF1 pixG, + out AF1 pixB, + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + out AF1 pixA, + #endif + AU2 ip, // Integer pixel position in output. + AU4 con){ // Constant generated by RcasSetup(). + // Algorithm uses minimal 3x3 pixel neighborhood. + // b + // d e f + // h + ASU2 sp=ASU2(ip); + AF3 b=FsrRcasLoadF(sp+ASU2( 0,-1)).rgb; + AF3 d=FsrRcasLoadF(sp+ASU2(-1, 0)).rgb; + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + AF4 ee=FsrRcasLoadF(sp); + AF3 e=ee.rgb;pixA=ee.a; + #else + AF3 e=FsrRcasLoadF(sp).rgb; + #endif + AF3 f=FsrRcasLoadF(sp+ASU2( 1, 0)).rgb; + AF3 h=FsrRcasLoadF(sp+ASU2( 0, 1)).rgb; + // Rename (32-bit) or regroup (16-bit). + AF1 bR=b.r; + AF1 bG=b.g; + AF1 bB=b.b; + AF1 dR=d.r; + AF1 dG=d.g; + AF1 dB=d.b; + AF1 eR=e.r; + AF1 eG=e.g; + AF1 eB=e.b; + AF1 fR=f.r; + AF1 fG=f.g; + AF1 fB=f.b; + AF1 hR=h.r; + AF1 hG=h.g; + AF1 hB=h.b; + // Run optional input transform. + FsrRcasInputF(bR,bG,bB); + FsrRcasInputF(dR,dG,dB); + FsrRcasInputF(eR,eG,eB); + FsrRcasInputF(fR,fG,fB); + FsrRcasInputF(hR,hG,hB); + // Luma times 2. + AF1 bL=bB*AF1_(0.5)+(bR*AF1_(0.5)+bG); + AF1 dL=dB*AF1_(0.5)+(dR*AF1_(0.5)+dG); + AF1 eL=eB*AF1_(0.5)+(eR*AF1_(0.5)+eG); + AF1 fL=fB*AF1_(0.5)+(fR*AF1_(0.5)+fG); + AF1 hL=hB*AF1_(0.5)+(hR*AF1_(0.5)+hG); + // Noise detection. + AF1 nz=AF1_(0.25)*bL+AF1_(0.25)*dL+AF1_(0.25)*fL+AF1_(0.25)*hL-eL; + nz=ASatF1(abs(nz)*APrxMedRcpF1(AMax3F1(AMax3F1(bL,dL,eL),fL,hL)-AMin3F1(AMin3F1(bL,dL,eL),fL,hL))); + nz=AF1_(-0.5)*nz+AF1_(1.0); + // Min and max of ring. + AF1 mn4R=min(AMin3F1(bR,dR,fR),hR); + AF1 mn4G=min(AMin3F1(bG,dG,fG),hG); + AF1 mn4B=min(AMin3F1(bB,dB,fB),hB); + AF1 mx4R=max(AMax3F1(bR,dR,fR),hR); + AF1 mx4G=max(AMax3F1(bG,dG,fG),hG); + AF1 mx4B=max(AMax3F1(bB,dB,fB),hB); + // Immediate constants for peak range. + AF2 peakC=AF2(1.0,-1.0*4.0); + // Limiters, these need to be high precision RCPs. + AF1 hitMinR=mn4R*ARcpF1(AF1_(4.0)*mx4R); + AF1 hitMinG=mn4G*ARcpF1(AF1_(4.0)*mx4G); + AF1 hitMinB=mn4B*ARcpF1(AF1_(4.0)*mx4B); + AF1 hitMaxR=(peakC.x-mx4R)*ARcpF1(AF1_(4.0)*mn4R+peakC.y); + AF1 hitMaxG=(peakC.x-mx4G)*ARcpF1(AF1_(4.0)*mn4G+peakC.y); + AF1 hitMaxB=(peakC.x-mx4B)*ARcpF1(AF1_(4.0)*mn4B+peakC.y); + AF1 lobeR=max(-hitMinR,hitMaxR); + AF1 lobeG=max(-hitMinG,hitMaxG); + AF1 lobeB=max(-hitMinB,hitMaxB); + AF1 lobe=max(AF1_(-FSR_RCAS_LIMIT),min(AMax3F1(lobeR,lobeG,lobeB),AF1_(0.0)))*AF1_AU1(con.x); + // Apply noise removal. + #ifdef FSR_RCAS_DENOISE + lobe*=nz; + #endif + // Resolve, which needs the medium precision rcp approximation to avoid visible tonality changes. + AF1 rcpL=APrxMedRcpF1(AF1_(4.0)*lobe+AF1_(1.0)); + pixR=(lobe*bR+lobe*dR+lobe*hR+lobe*fR+eR)*rcpL; + pixG=(lobe*bG+lobe*dG+lobe*hG+lobe*fG+eG)*rcpL; + pixB=(lobe*bB+lobe*dB+lobe*hB+lobe*fB+eB)*rcpL; + return;} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// NON-PACKED 16-BIT VERSION +//============================================================================================================================== +#if defined(A_GPU)&&defined(A_HALF)&&defined(FSR_RCAS_H) + // Input callback prototypes that need to be implemented by calling shader + AH4 FsrRcasLoadH(ASW2 p); + void FsrRcasInputH(inout AH1 r,inout AH1 g,inout AH1 b); +//------------------------------------------------------------------------------------------------------------------------------ + void FsrRcasH( + out AH1 pixR, // Output values, non-vector so port between RcasFilter() and RcasFilterH() is easy. + out AH1 pixG, + out AH1 pixB, + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + out AH1 pixA, + #endif + AU2 ip, // Integer pixel position in output. + AU4 con){ // Constant generated by RcasSetup(). + // Sharpening algorithm uses minimal 3x3 pixel neighborhood. + // b + // d e f + // h + ASW2 sp=ASW2(ip); + AH3 b=FsrRcasLoadH(sp+ASW2( 0,-1)).rgb; + AH3 d=FsrRcasLoadH(sp+ASW2(-1, 0)).rgb; + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + AH4 ee=FsrRcasLoadH(sp); + AH3 e=ee.rgb;pixA=ee.a; + #else + AH3 e=FsrRcasLoadH(sp).rgb; + #endif + AH3 f=FsrRcasLoadH(sp+ASW2( 1, 0)).rgb; + AH3 h=FsrRcasLoadH(sp+ASW2( 0, 1)).rgb; + // Rename (32-bit) or regroup (16-bit). + AH1 bR=b.r; + AH1 bG=b.g; + AH1 bB=b.b; + AH1 dR=d.r; + AH1 dG=d.g; + AH1 dB=d.b; + AH1 eR=e.r; + AH1 eG=e.g; + AH1 eB=e.b; + AH1 fR=f.r; + AH1 fG=f.g; + AH1 fB=f.b; + AH1 hR=h.r; + AH1 hG=h.g; + AH1 hB=h.b; + // Run optional input transform. + FsrRcasInputH(bR,bG,bB); + FsrRcasInputH(dR,dG,dB); + FsrRcasInputH(eR,eG,eB); + FsrRcasInputH(fR,fG,fB); + FsrRcasInputH(hR,hG,hB); + // Luma times 2. + AH1 bL=bB*AH1_(0.5)+(bR*AH1_(0.5)+bG); + AH1 dL=dB*AH1_(0.5)+(dR*AH1_(0.5)+dG); + AH1 eL=eB*AH1_(0.5)+(eR*AH1_(0.5)+eG); + AH1 fL=fB*AH1_(0.5)+(fR*AH1_(0.5)+fG); + AH1 hL=hB*AH1_(0.5)+(hR*AH1_(0.5)+hG); + // Noise detection. + AH1 nz=AH1_(0.25)*bL+AH1_(0.25)*dL+AH1_(0.25)*fL+AH1_(0.25)*hL-eL; + nz=ASatH1(abs(nz)*APrxMedRcpH1(AMax3H1(AMax3H1(bL,dL,eL),fL,hL)-AMin3H1(AMin3H1(bL,dL,eL),fL,hL))); + nz=AH1_(-0.5)*nz+AH1_(1.0); + // Min and max of ring. + AH1 mn4R=min(AMin3H1(bR,dR,fR),hR); + AH1 mn4G=min(AMin3H1(bG,dG,fG),hG); + AH1 mn4B=min(AMin3H1(bB,dB,fB),hB); + AH1 mx4R=max(AMax3H1(bR,dR,fR),hR); + AH1 mx4G=max(AMax3H1(bG,dG,fG),hG); + AH1 mx4B=max(AMax3H1(bB,dB,fB),hB); + // Immediate constants for peak range. + AH2 peakC=AH2(1.0,-1.0*4.0); + // Limiters, these need to be high precision RCPs. + AH1 hitMinR=mn4R*ARcpH1(AH1_(4.0)*mx4R); + AH1 hitMinG=mn4G*ARcpH1(AH1_(4.0)*mx4G); + AH1 hitMinB=mn4B*ARcpH1(AH1_(4.0)*mx4B); + AH1 hitMaxR=(peakC.x-mx4R)*ARcpH1(AH1_(4.0)*mn4R+peakC.y); + AH1 hitMaxG=(peakC.x-mx4G)*ARcpH1(AH1_(4.0)*mn4G+peakC.y); + AH1 hitMaxB=(peakC.x-mx4B)*ARcpH1(AH1_(4.0)*mn4B+peakC.y); + AH1 lobeR=max(-hitMinR,hitMaxR); + AH1 lobeG=max(-hitMinG,hitMaxG); + AH1 lobeB=max(-hitMinB,hitMaxB); + AH1 lobe=max(AH1_(-FSR_RCAS_LIMIT),min(AMax3H1(lobeR,lobeG,lobeB),AH1_(0.0)))*AH2_AU1(con.y).x; + // Apply noise removal. + #ifdef FSR_RCAS_DENOISE + lobe*=nz; + #endif + // Resolve, which needs the medium precision rcp approximation to avoid visible tonality changes. + AH1 rcpL=APrxMedRcpH1(AH1_(4.0)*lobe+AH1_(1.0)); + pixR=(lobe*bR+lobe*dR+lobe*hR+lobe*fR+eR)*rcpL; + pixG=(lobe*bG+lobe*dG+lobe*hG+lobe*fG+eG)*rcpL; + pixB=(lobe*bB+lobe*dB+lobe*hB+lobe*fB+eB)*rcpL;} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// PACKED 16-BIT VERSION +//============================================================================================================================== +#if defined(A_GPU)&&defined(A_HALF)&&defined(FSR_RCAS_HX2) + // Input callback prototypes that need to be implemented by the calling shader + AH4 FsrRcasLoadHx2(ASW2 p); + void FsrRcasInputHx2(inout AH2 r,inout AH2 g,inout AH2 b); +//------------------------------------------------------------------------------------------------------------------------------ + // Can be used to convert from packed Structures of Arrays to Arrays of Structures for store. + void FsrRcasDepackHx2(out AH4 pix0,out AH4 pix1,AH2 pixR,AH2 pixG,AH2 pixB){ + #ifdef A_HLSL + // Invoke a slower path for DX only, since it won't allow uninitialized values. + pix0.a=pix1.a=0.0; + #endif + pix0.rgb=AH3(pixR.x,pixG.x,pixB.x); + pix1.rgb=AH3(pixR.y,pixG.y,pixB.y);} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrRcasHx2( + // Output values are for 2 8x8 tiles in a 16x8 region. + // pix.x = left 8x8 tile + // pix.y = right 8x8 tile + // This enables later processing to easily be packed as well. + out AH2 pixR, + out AH2 pixG, + out AH2 pixB, + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + out AH2 pixA, + #endif + AU2 ip, // Integer pixel position in output. + AU4 con){ // Constant generated by RcasSetup(). + // No scaling algorithm uses minimal 3x3 pixel neighborhood. + ASW2 sp0=ASW2(ip); + AH3 b0=FsrRcasLoadHx2(sp0+ASW2( 0,-1)).rgb; + AH3 d0=FsrRcasLoadHx2(sp0+ASW2(-1, 0)).rgb; + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + AH4 ee0=FsrRcasLoadHx2(sp0); + AH3 e0=ee0.rgb;pixA.r=ee0.a; + #else + AH3 e0=FsrRcasLoadHx2(sp0).rgb; + #endif + AH3 f0=FsrRcasLoadHx2(sp0+ASW2( 1, 0)).rgb; + AH3 h0=FsrRcasLoadHx2(sp0+ASW2( 0, 1)).rgb; + ASW2 sp1=sp0+ASW2(8,0); + AH3 b1=FsrRcasLoadHx2(sp1+ASW2( 0,-1)).rgb; + AH3 d1=FsrRcasLoadHx2(sp1+ASW2(-1, 0)).rgb; + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + AH4 ee1=FsrRcasLoadHx2(sp1); + AH3 e1=ee1.rgb;pixA.g=ee1.a; + #else + AH3 e1=FsrRcasLoadHx2(sp1).rgb; + #endif + AH3 f1=FsrRcasLoadHx2(sp1+ASW2( 1, 0)).rgb; + AH3 h1=FsrRcasLoadHx2(sp1+ASW2( 0, 1)).rgb; + // Arrays of Structures to Structures of Arrays conversion. + AH2 bR=AH2(b0.r,b1.r); + AH2 bG=AH2(b0.g,b1.g); + AH2 bB=AH2(b0.b,b1.b); + AH2 dR=AH2(d0.r,d1.r); + AH2 dG=AH2(d0.g,d1.g); + AH2 dB=AH2(d0.b,d1.b); + AH2 eR=AH2(e0.r,e1.r); + AH2 eG=AH2(e0.g,e1.g); + AH2 eB=AH2(e0.b,e1.b); + AH2 fR=AH2(f0.r,f1.r); + AH2 fG=AH2(f0.g,f1.g); + AH2 fB=AH2(f0.b,f1.b); + AH2 hR=AH2(h0.r,h1.r); + AH2 hG=AH2(h0.g,h1.g); + AH2 hB=AH2(h0.b,h1.b); + // Run optional input transform. + FsrRcasInputHx2(bR,bG,bB); + FsrRcasInputHx2(dR,dG,dB); + FsrRcasInputHx2(eR,eG,eB); + FsrRcasInputHx2(fR,fG,fB); + FsrRcasInputHx2(hR,hG,hB); + // Luma times 2. + AH2 bL=bB*AH2_(0.5)+(bR*AH2_(0.5)+bG); + AH2 dL=dB*AH2_(0.5)+(dR*AH2_(0.5)+dG); + AH2 eL=eB*AH2_(0.5)+(eR*AH2_(0.5)+eG); + AH2 fL=fB*AH2_(0.5)+(fR*AH2_(0.5)+fG); + AH2 hL=hB*AH2_(0.5)+(hR*AH2_(0.5)+hG); + // Noise detection. + AH2 nz=AH2_(0.25)*bL+AH2_(0.25)*dL+AH2_(0.25)*fL+AH2_(0.25)*hL-eL; + nz=ASatH2(abs(nz)*APrxMedRcpH2(AMax3H2(AMax3H2(bL,dL,eL),fL,hL)-AMin3H2(AMin3H2(bL,dL,eL),fL,hL))); + nz=AH2_(-0.5)*nz+AH2_(1.0); + // Min and max of ring. + AH2 mn4R=min(AMin3H2(bR,dR,fR),hR); + AH2 mn4G=min(AMin3H2(bG,dG,fG),hG); + AH2 mn4B=min(AMin3H2(bB,dB,fB),hB); + AH2 mx4R=max(AMax3H2(bR,dR,fR),hR); + AH2 mx4G=max(AMax3H2(bG,dG,fG),hG); + AH2 mx4B=max(AMax3H2(bB,dB,fB),hB); + // Immediate constants for peak range. + AH2 peakC=AH2(1.0,-1.0*4.0); + // Limiters, these need to be high precision RCPs. + AH2 hitMinR=mn4R*ARcpH2(AH2_(4.0)*mx4R); + AH2 hitMinG=mn4G*ARcpH2(AH2_(4.0)*mx4G); + AH2 hitMinB=mn4B*ARcpH2(AH2_(4.0)*mx4B); + AH2 hitMaxR=(peakC.x-mx4R)*ARcpH2(AH2_(4.0)*mn4R+peakC.y); + AH2 hitMaxG=(peakC.x-mx4G)*ARcpH2(AH2_(4.0)*mn4G+peakC.y); + AH2 hitMaxB=(peakC.x-mx4B)*ARcpH2(AH2_(4.0)*mn4B+peakC.y); + AH2 lobeR=max(-hitMinR,hitMaxR); + AH2 lobeG=max(-hitMinG,hitMaxG); + AH2 lobeB=max(-hitMinB,hitMaxB); + AH2 lobe=max(AH2_(-FSR_RCAS_LIMIT),min(AMax3H2(lobeR,lobeG,lobeB),AH2_(0.0)))*AH2_(AH2_AU1(con.y).x); + // Apply noise removal. + #ifdef FSR_RCAS_DENOISE + lobe*=nz; + #endif + // Resolve, which needs the medium precision rcp approximation to avoid visible tonality changes. + AH2 rcpL=APrxMedRcpH2(AH2_(4.0)*lobe+AH2_(1.0)); + pixR=(lobe*bR+lobe*dR+lobe*hR+lobe*fR+eR)*rcpL; + pixG=(lobe*bG+lobe*dG+lobe*hG+lobe*fG+eG)*rcpL; + pixB=(lobe*bB+lobe*dB+lobe*hB+lobe*fB+eB)*rcpL;} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// FSR - [LFGA] LINEAR FILM GRAIN APPLICATOR +// +//------------------------------------------------------------------------------------------------------------------------------ +// Adding output-resolution film grain after scaling is a good way to mask both rendering and scaling artifacts. +// Suggest using tiled blue noise as film grain input, with peak noise frequency set for a specific look and feel. +// The 'Lfga*()' functions provide a convenient way to introduce grain. +// These functions limit grain based on distance to signal limits. +// This is done so that the grain is temporally energy preserving, and thus won't modify image tonality. +// Grain application should be done in a linear colorspace. +// The grain should be temporally changing, but have a temporal sum per pixel that adds to zero (non-biased). +//------------------------------------------------------------------------------------------------------------------------------ +// Usage, +// FsrLfga*( +// color, // In/out linear colorspace color {0 to 1} ranged. +// grain, // Per pixel grain texture value {-0.5 to 0.5} ranged, input is 3-channel to support colored grain. +// amount); // Amount of grain (0 to 1} ranged. +//------------------------------------------------------------------------------------------------------------------------------ +// Example if grain texture is monochrome: 'FsrLfgaF(color,AF3_(grain),amount)' +//============================================================================================================================== +#if defined(A_GPU) + // Maximum grain is the minimum distance to the signal limit. + void FsrLfgaF(inout AF3 c,AF3 t,AF1 a){c+=(t*AF3_(a))*min(AF3_(1.0)-c,c);} +#endif +//============================================================================================================================== +#if defined(A_GPU)&&defined(A_HALF) + // Half precision version (slower). + void FsrLfgaH(inout AH3 c,AH3 t,AH1 a){c+=(t*AH3_(a))*min(AH3_(1.0)-c,c);} +//------------------------------------------------------------------------------------------------------------------------------ + // Packed half precision version (faster). + void FsrLfgaHx2(inout AH2 cR,inout AH2 cG,inout AH2 cB,AH2 tR,AH2 tG,AH2 tB,AH1 a){ + cR+=(tR*AH2_(a))*min(AH2_(1.0)-cR,cR);cG+=(tG*AH2_(a))*min(AH2_(1.0)-cG,cG);cB+=(tB*AH2_(a))*min(AH2_(1.0)-cB,cB);} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// FSR - [SRTM] SIMPLE REVERSIBLE TONE-MAPPER +// +//------------------------------------------------------------------------------------------------------------------------------ +// This provides a way to take linear HDR color {0 to FP16_MAX} and convert it into a temporary {0 to 1} ranged post-tonemapped linear. +// The tonemapper preserves RGB ratio, which helps maintain HDR color bleed during filtering. +//------------------------------------------------------------------------------------------------------------------------------ +// Reversible tonemapper usage, +// FsrSrtm*(color); // {0 to FP16_MAX} converted to {0 to 1}. +// FsrSrtmInv*(color); // {0 to 1} converted into {0 to 32768, output peak safe for FP16}. +//============================================================================================================================== +#if defined(A_GPU) + void FsrSrtmF(inout AF3 c){c*=AF3_(ARcpF1(AMax3F1(c.r,c.g,c.b)+AF1_(1.0)));} + // The extra max solves the c=1.0 case (which is a /0). + void FsrSrtmInvF(inout AF3 c){c*=AF3_(ARcpF1(max(AF1_(1.0/32768.0),AF1_(1.0)-AMax3F1(c.r,c.g,c.b))));} +#endif +//============================================================================================================================== +#if defined(A_GPU)&&defined(A_HALF) + void FsrSrtmH(inout AH3 c){c*=AH3_(ARcpH1(AMax3H1(c.r,c.g,c.b)+AH1_(1.0)));} + void FsrSrtmInvH(inout AH3 c){c*=AH3_(ARcpH1(max(AH1_(1.0/32768.0),AH1_(1.0)-AMax3H1(c.r,c.g,c.b))));} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrSrtmHx2(inout AH2 cR,inout AH2 cG,inout AH2 cB){ + AH2 rcp=ARcpH2(AMax3H2(cR,cG,cB)+AH2_(1.0));cR*=rcp;cG*=rcp;cB*=rcp;} + void FsrSrtmInvHx2(inout AH2 cR,inout AH2 cG,inout AH2 cB){ + AH2 rcp=ARcpH2(max(AH2_(1.0/32768.0),AH2_(1.0)-AMax3H2(cR,cG,cB)));cR*=rcp;cG*=rcp;cB*=rcp;} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// FSR - [TEPD] TEMPORAL ENERGY PRESERVING DITHER +// +//------------------------------------------------------------------------------------------------------------------------------ +// Temporally energy preserving dithered {0 to 1} linear to gamma 2.0 conversion. +// Gamma 2.0 is used so that the conversion back to linear is just to square the color. +// The conversion comes in 8-bit and 10-bit modes, designed for output to 8-bit UNORM or 10:10:10:2 respectively. +// Given good non-biased temporal blue noise as dither input, +// the output dither will temporally conserve energy. +// This is done by choosing the linear nearest step point instead of perceptual nearest. +// See code below for details. +//------------------------------------------------------------------------------------------------------------------------------ +// DX SPEC RULES FOR FLOAT->UNORM 8-BIT CONVERSION +// =============================================== +// - Output is 'uint(floor(saturate(n)*255.0+0.5))'. +// - Thus rounding is to nearest. +// - NaN gets converted to zero. +// - INF is clamped to {0.0 to 1.0}. +//============================================================================================================================== +#if defined(A_GPU) + // Hand tuned integer position to dither value, with more values than simple checkerboard. + // Only 32-bit has enough precision for this compddation. + // Output is {0 to <1}. + AF1 FsrTepdDitF(AU2 p,AU1 f){ + AF1 x=AF1_(p.x+f); + AF1 y=AF1_(p.y); + // The 1.61803 golden ratio. + AF1 a=AF1_((1.0+sqrt(5.0))/2.0); + // Number designed to provide a good visual pattern. + AF1 b=AF1_(1.0/3.69); + x=x*a+(y*b); + return AFractF1(x);} +//------------------------------------------------------------------------------------------------------------------------------ + // This version is 8-bit gamma 2.0. + // The 'c' input is {0 to 1}. + // Output is {0 to 1} ready for image store. + void FsrTepdC8F(inout AF3 c,AF1 dit){ + AF3 n=sqrt(c); + n=floor(n*AF3_(255.0))*AF3_(1.0/255.0); + AF3 a=n*n; + AF3 b=n+AF3_(1.0/255.0);b=b*b; + // Ratio of 'a' to 'b' required to produce 'c'. + // APrxLoRcpF1() won't work here (at least for very high dynamic ranges). + // APrxMedRcpF1() is an IADD,FMA,MUL. + AF3 r=(c-b)*APrxMedRcpF3(a-b); + // Use the ratio as a cutoff to choose 'a' or 'b'. + // AGtZeroF1() is a MUL. + c=ASatF3(n+AGtZeroF3(AF3_(dit)-r)*AF3_(1.0/255.0));} +//------------------------------------------------------------------------------------------------------------------------------ + // This version is 10-bit gamma 2.0. + // The 'c' input is {0 to 1}. + // Output is {0 to 1} ready for image store. + void FsrTepdC10F(inout AF3 c,AF1 dit){ + AF3 n=sqrt(c); + n=floor(n*AF3_(1023.0))*AF3_(1.0/1023.0); + AF3 a=n*n; + AF3 b=n+AF3_(1.0/1023.0);b=b*b; + AF3 r=(c-b)*APrxMedRcpF3(a-b); + c=ASatF3(n+AGtZeroF3(AF3_(dit)-r)*AF3_(1.0/1023.0));} +#endif +//============================================================================================================================== +#if defined(A_GPU)&&defined(A_HALF) + AH1 FsrTepdDitH(AU2 p,AU1 f){ + AF1 x=AF1_(p.x+f); + AF1 y=AF1_(p.y); + AF1 a=AF1_((1.0+sqrt(5.0))/2.0); + AF1 b=AF1_(1.0/3.69); + x=x*a+(y*b); + return AH1(AFractF1(x));} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrTepdC8H(inout AH3 c,AH1 dit){ + AH3 n=sqrt(c); + n=floor(n*AH3_(255.0))*AH3_(1.0/255.0); + AH3 a=n*n; + AH3 b=n+AH3_(1.0/255.0);b=b*b; + AH3 r=(c-b)*APrxMedRcpH3(a-b); + c=ASatH3(n+AGtZeroH3(AH3_(dit)-r)*AH3_(1.0/255.0));} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrTepdC10H(inout AH3 c,AH1 dit){ + AH3 n=sqrt(c); + n=floor(n*AH3_(1023.0))*AH3_(1.0/1023.0); + AH3 a=n*n; + AH3 b=n+AH3_(1.0/1023.0);b=b*b; + AH3 r=(c-b)*APrxMedRcpH3(a-b); + c=ASatH3(n+AGtZeroH3(AH3_(dit)-r)*AH3_(1.0/1023.0));} +//============================================================================================================================== + // This computes dither for positions 'p' and 'p+{8,0}'. + AH2 FsrTepdDitHx2(AU2 p,AU1 f){ + AF2 x; + x.x=AF1_(p.x+f); + x.y=x.x+AF1_(8.0); + AF1 y=AF1_(p.y); + AF1 a=AF1_((1.0+sqrt(5.0))/2.0); + AF1 b=AF1_(1.0/3.69); + x=x*AF2_(a)+AF2_(y*b); + return AH2(AFractF2(x));} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrTepdC8Hx2(inout AH2 cR,inout AH2 cG,inout AH2 cB,AH2 dit){ + AH2 nR=sqrt(cR); + AH2 nG=sqrt(cG); + AH2 nB=sqrt(cB); + nR=floor(nR*AH2_(255.0))*AH2_(1.0/255.0); + nG=floor(nG*AH2_(255.0))*AH2_(1.0/255.0); + nB=floor(nB*AH2_(255.0))*AH2_(1.0/255.0); + AH2 aR=nR*nR; + AH2 aG=nG*nG; + AH2 aB=nB*nB; + AH2 bR=nR+AH2_(1.0/255.0);bR=bR*bR; + AH2 bG=nG+AH2_(1.0/255.0);bG=bG*bG; + AH2 bB=nB+AH2_(1.0/255.0);bB=bB*bB; + AH2 rR=(cR-bR)*APrxMedRcpH2(aR-bR); + AH2 rG=(cG-bG)*APrxMedRcpH2(aG-bG); + AH2 rB=(cB-bB)*APrxMedRcpH2(aB-bB); + cR=ASatH2(nR+AGtZeroH2(dit-rR)*AH2_(1.0/255.0)); + cG=ASatH2(nG+AGtZeroH2(dit-rG)*AH2_(1.0/255.0)); + cB=ASatH2(nB+AGtZeroH2(dit-rB)*AH2_(1.0/255.0));} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrTepdC10Hx2(inout AH2 cR,inout AH2 cG,inout AH2 cB,AH2 dit){ + AH2 nR=sqrt(cR); + AH2 nG=sqrt(cG); + AH2 nB=sqrt(cB); + nR=floor(nR*AH2_(1023.0))*AH2_(1.0/1023.0); + nG=floor(nG*AH2_(1023.0))*AH2_(1.0/1023.0); + nB=floor(nB*AH2_(1023.0))*AH2_(1.0/1023.0); + AH2 aR=nR*nR; + AH2 aG=nG*nG; + AH2 aB=nB*nB; + AH2 bR=nR+AH2_(1.0/1023.0);bR=bR*bR; + AH2 bG=nG+AH2_(1.0/1023.0);bG=bG*bG; + AH2 bB=nB+AH2_(1.0/1023.0);bB=bB*bB; + AH2 rR=(cR-bR)*APrxMedRcpH2(aR-bR); + AH2 rG=(cG-bG)*APrxMedRcpH2(aG-bG); + AH2 rB=(cB-bB)*APrxMedRcpH2(aB-bB); + cR=ASatH2(nR+AGtZeroH2(dit-rR)*AH2_(1.0/1023.0)); + cG=ASatH2(nG+AGtZeroH2(dit-rG)*AH2_(1.0/1023.0)); + cB=ASatH2(nB+AGtZeroH2(dit-rB)*AH2_(1.0/1023.0));} +#endif diff --git a/examples/46-fsr/fs_fsr_copy_linear_to_gamma.sc b/examples/46-fsr/fs_fsr_copy_linear_to_gamma.sc new file mode 100644 index 000000000..6e6a3235b --- /dev/null +++ b/examples/46-fsr/fs_fsr_copy_linear_to_gamma.sc @@ -0,0 +1,21 @@ +$input v_texcoord0 + +/* +* Copyright 2021 elven cache. All rights reserved. +* License: https://github.com/bkaradzic/bgfx#license-bsd-2-clause +*/ + +#include "../common/common.sh" + +SAMPLER2D(s_color, 0); + +void main() +{ + vec2 texCoord = v_texcoord0; + vec4 linearColor = texture2D(s_color, texCoord); + + // this pass is writing directly out to backbuffer, convert from linear to gamma + vec4 color = vec4(toGamma(linearColor.xyz), linearColor.w); + + gl_FragColor = color; +} diff --git a/examples/46-fsr/fs_fsr_forward.sc b/examples/46-fsr/fs_fsr_forward.sc new file mode 100644 index 000000000..fe02a3208 --- /dev/null +++ b/examples/46-fsr/fs_fsr_forward.sc @@ -0,0 +1,79 @@ +$input v_normal, v_texcoord0, v_texcoord1, v_texcoord2 + +/* +* Copyright 2021 elven cache. All rights reserved. +* License: https://github.com/bkaradzic/bgfx#license-bsd-2-clause +*/ + +#include "../common/common.sh" + +SAMPLER2D(s_albedo, 0); +SAMPLER2D(s_normal, 1); + +// struct ModelUniforms +uniform vec4 u_modelParams[2]; + +#define u_color (u_modelParams[0].xyz) +#define u_lightPosition (u_modelParams[1].xyz) + +// http://www.thetenthplanet.de/archives/1180 +// "followup: normal mapping without precomputed tangents" +mat3 cotangentFrame(vec3 N, vec3 p, vec2 uv) +{ + // get edge vectors of the pixel triangle + vec3 dp1 = dFdx(p); + vec3 dp2 = dFdy(p); + vec2 duv1 = dFdx(uv); + vec2 duv2 = dFdy(uv); + + // solve the linear system + vec3 dp2perp = cross(dp2, N); + vec3 dp1perp = cross(N, dp1); + vec3 T = dp2perp * duv1.x + dp1perp * duv2.x; + vec3 B = dp2perp * duv1.y + dp1perp * duv2.y; + + // construct a scale-invariant frame + float invMax = inversesqrt(max(dot(T,T), dot(B,B))); + return mat3(T*invMax, B*invMax, N); +} + +void main() +{ + vec3 albedo = toLinear(texture2D(s_albedo, v_texcoord0).xyz); + + // get vertex normal + vec3 normal = normalize(v_normal); + + // get normal map normal, unpack, and calculate z + vec3 normalMap; + normalMap.xy = texture2D(s_normal, v_texcoord0).xy; + normalMap.xy = normalMap.xy * 2.0 - 1.0; + normalMap.z = sqrt(1.0 - dot(normalMap.xy, normalMap.xy)); + + // swap x and y, because the brick texture looks flipped, don't copy this... + normalMap.xy = -normalMap.yx; + + // perturb geometry normal by normal map + vec3 pos = v_texcoord1.xyz; // contains world space pos + mat3 TBN = cotangentFrame(normal, pos, v_texcoord0); + vec3 bumpedNormal = normalize(instMul(TBN, normalMap)); + + vec3 light = (u_lightPosition - pos); + light = normalize(light); + + float NdotL = saturate(dot(bumpedNormal, light)); + float diffuse = NdotL * 1.0; + + vec3 V = v_texcoord2.xyz; // contains view vector + vec3 H = normalize(V+light); + float NdotH = saturate(dot(bumpedNormal, H)); + float specular = 5.0 * pow(NdotH, 256); + float ambient = 0.1; + + float lightAmount = ambient + diffuse; + vec3 color = u_color * albedo * lightAmount + specular; + + // leave color in linear space for better dof filter result + + gl_FragColor = vec4(color, 1.0); +} diff --git a/examples/46-fsr/fs_fsr_forward_grid.sc b/examples/46-fsr/fs_fsr_forward_grid.sc new file mode 100644 index 000000000..77d167f81 --- /dev/null +++ b/examples/46-fsr/fs_fsr_forward_grid.sc @@ -0,0 +1,58 @@ +$input v_normal, v_texcoord0, v_texcoord1, v_texcoord2 + +/* +* Copyright 2021 elven cache. All rights reserved. +* License: https://github.com/bkaradzic/bgfx#license-bsd-2-clause +*/ + +#include "../common/common.sh" + +// struct ModelUniforms +uniform vec4 u_modelParams[2]; + +#define u_color (u_modelParams[0].xyz) +#define u_lightPosition (u_modelParams[1].xyz) + + +int ModHelper (float a, float b) +{ + return int( a - (b*floor(a/b))); +} + +vec3 GetGridColor (vec2 position, float width, vec3 color) +{ + position = abs(floor( position + vec2(-width, -width) )); + int posXMod = ModHelper(position.x, 2.0); + int posYMod = ModHelper(position.y, 2.0); + float gridColorScale = (posXMod == posYMod) ? 0.75 : 1.25; + return toLinear(color) * gridColorScale; +} + +void main() +{ + vec3 worldSpacePosition = v_texcoord1.xyz; // contains ws pos + vec2 gridCoord = worldSpacePosition.xz; // assuming y is up + vec3 gridColor = GetGridColor(gridCoord.xy, 0.002, u_color); + + // get vertex normal + vec3 normal = normalize(v_normal); + + vec3 light = (u_lightPosition - worldSpacePosition); + light = normalize(light); + + float NdotL = saturate(dot(normal, light)); + float diffuse = NdotL * 1.0; + + vec3 V = v_texcoord2.xyz; // contains view vector + vec3 H = normalize(V+light); + float NdotH = saturate(dot(normal, H)); + float specular = 5.0 * pow(NdotH, 256); + float ambient = 0.1; + + float lightAmount = ambient + diffuse; + vec3 color = gridColor * lightAmount + specular; + + // leave color in linear space for better dof filter result + + gl_FragColor = vec4(color, 1.0); +} diff --git a/examples/46-fsr/fsr.cpp b/examples/46-fsr/fsr.cpp new file mode 100644 index 000000000..981e61fcc --- /dev/null +++ b/examples/46-fsr/fsr.cpp @@ -0,0 +1,248 @@ +/* +* Copyright 2021 Richard Schubert. All rights reserved. +* License: https://github.com/bkaradzic/bgfx#license-bsd-2-clause +* +* AMD FidelityFX Super Resolution 1.0 (FSR) +* Based on https://github.com/GPUOpen-Effects/FidelityFX-FSR/blob/master/sample/ +*/ + +#include "fsr.h" + +#include +#include + +struct FsrResources +{ + struct Uniforms + { + struct Vec4 + { + float x; + float y; + float z; + float w; + }; + + enum + { + NumVec4 = 3 + }; + + void init() + { + u_params = bgfx::createUniform("u_params", bgfx::UniformType::Vec4, NumVec4); + }; + + void submit() const + { + bgfx::setUniform(u_params, m_params, NumVec4); + } + + void destroy() + { + bgfx::destroy(u_params); + } + + union + { + struct + { + Vec4 ViewportSizeRcasAttenuation; + Vec4 SrcSize; + Vec4 DstSize; + }; + + uint32_t m_params[NumVec4 * 4]; + }; + + bgfx::UniformHandle u_params{ BGFX_INVALID_HANDLE }; + }; + + uint32_t m_width{ 0 }; + uint32_t m_height{ 0 }; + + // Resource handles + bgfx::ProgramHandle m_bilinear16Program{ BGFX_INVALID_HANDLE }; + bgfx::ProgramHandle m_bilinear32Program{ BGFX_INVALID_HANDLE }; + bgfx::ProgramHandle m_easu16Program{ BGFX_INVALID_HANDLE }; + bgfx::ProgramHandle m_easu32Program{ BGFX_INVALID_HANDLE }; + bgfx::ProgramHandle m_rcas16Program{ BGFX_INVALID_HANDLE }; + bgfx::ProgramHandle m_rcas32Program{ BGFX_INVALID_HANDLE }; + + // Shader uniforms + Uniforms m_uniforms; + + // Uniforms to indentify texture samplers + bgfx::UniformHandle s_inputTexture{ BGFX_INVALID_HANDLE }; + + bgfx::TextureHandle m_easuTexture16F{ BGFX_INVALID_HANDLE }; + bgfx::TextureHandle m_rcasTexture16F{ BGFX_INVALID_HANDLE }; + bgfx::TextureHandle m_easuTexture32F{ BGFX_INVALID_HANDLE }; + bgfx::TextureHandle m_rcasTexture32F{ BGFX_INVALID_HANDLE }; +}; + + +Fsr::Fsr() +{ + m_resources = new FsrResources(); +} + +Fsr::~Fsr() +{ + delete m_resources; +} + +void Fsr::init(uint32_t _width, uint32_t _height) +{ + resize(_width, _height); + + // Create uniforms for screen passes and models + m_resources->m_uniforms.init(); + + // Create texture sampler uniforms (used when we bind textures) + m_resources->s_inputTexture = bgfx::createUniform("InputTexture", bgfx::UniformType::Sampler); + + // Create program from shaders. + m_resources->m_bilinear32Program = bgfx::createProgram(loadShader("cs_fsr_bilinear_32"), true); + m_resources->m_easu32Program = bgfx::createProgram(loadShader("cs_fsr_easu_32"), true); + m_resources->m_rcas32Program = bgfx::createProgram(loadShader("cs_fsr_rcas_32"), true); + + m_support16BitPrecision = (bgfx::getRendererType() != bgfx::RendererType::OpenGL); + if (m_support16BitPrecision) + { + m_resources->m_bilinear16Program = bgfx::createProgram(loadShader("cs_fsr_bilinear_16"), true); + m_resources->m_easu16Program = bgfx::createProgram(loadShader("cs_fsr_easu_16"), true); + m_resources->m_rcas16Program = bgfx::createProgram(loadShader("cs_fsr_rcas_16"), true); + } +} + +void Fsr::destroy() +{ + if(m_support16BitPrecision) + { + bgfx::destroy(m_resources->m_bilinear16Program); + bgfx::destroy(m_resources->m_easu16Program); + bgfx::destroy(m_resources->m_rcas16Program); + } + + bgfx::destroy(m_resources->m_bilinear32Program); + bgfx::destroy(m_resources->m_easu32Program); + bgfx::destroy(m_resources->m_rcas32Program); + + m_resources->m_uniforms.destroy(); + + bgfx::destroy(m_resources->s_inputTexture); + + if (m_support16BitPrecision) + { + bgfx::destroy(m_resources->m_easuTexture16F); + bgfx::destroy(m_resources->m_rcasTexture16F); + } + + bgfx::destroy(m_resources->m_easuTexture32F); + bgfx::destroy(m_resources->m_rcasTexture32F); +} + +void Fsr::resize(uint32_t _width, uint32_t _height) +{ + m_resources->m_width = _width; + m_resources->m_height = _height; + + if(m_resources->m_easuTexture16F.idx != bgfx::kInvalidHandle) + { + if (m_support16BitPrecision) + { + bgfx::destroy(m_resources->m_easuTexture16F); + bgfx::destroy(m_resources->m_rcasTexture16F); + } + bgfx::destroy(m_resources->m_easuTexture32F); + bgfx::destroy(m_resources->m_rcasTexture32F); + } + + if (m_support16BitPrecision) + { + m_resources->m_easuTexture16F = bgfx::createTexture2D(uint16_t(m_resources->m_width), uint16_t(m_resources->m_height), false, 1, bgfx::TextureFormat::RGBA16F, BGFX_TEXTURE_COMPUTE_WRITE | BGFX_SAMPLER_MIN_POINT | BGFX_SAMPLER_MAG_POINT | BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP); + m_resources->m_rcasTexture16F = bgfx::createTexture2D(uint16_t(m_resources->m_width), uint16_t(m_resources->m_height), false, 1, bgfx::TextureFormat::RGBA16F, BGFX_TEXTURE_COMPUTE_WRITE | BGFX_SAMPLER_MIN_POINT | BGFX_SAMPLER_MAG_POINT | BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP); + } + m_resources->m_easuTexture32F = bgfx::createTexture2D(uint16_t(m_resources->m_width), uint16_t(m_resources->m_height), false, 1, bgfx::TextureFormat::RGBA32F, BGFX_TEXTURE_COMPUTE_WRITE | BGFX_SAMPLER_MIN_POINT | BGFX_SAMPLER_MAG_POINT | BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP); + m_resources->m_rcasTexture32F = bgfx::createTexture2D(uint16_t(m_resources->m_width), uint16_t(m_resources->m_height), false, 1, bgfx::TextureFormat::RGBA32F, BGFX_TEXTURE_COMPUTE_WRITE | BGFX_SAMPLER_MIN_POINT | BGFX_SAMPLER_MAG_POINT | BGFX_SAMPLER_U_CLAMP | BGFX_SAMPLER_V_CLAMP); +} + +bgfx::ViewId Fsr::computeFsr(bgfx::ViewId _pass, bgfx::TextureHandle _colorTexture) +{ + assert(!m_config.m_fsr16Bit || m_support16BitPrecision); + + updateUniforms(); + + bgfx::ViewId view = _pass; + + // This value is the image region dimension that each thread group of the FSR shader operates on + static constexpr int threadGroupWorkRegionDim = 16; + int const dispatchX = (m_resources->m_width + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim; + int const dispatchY = (m_resources->m_height + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim; + bgfx::TextureFormat::Enum const format = m_config.m_fsr16Bit ? bgfx::TextureFormat::RGBA16F : bgfx::TextureFormat::RGBA32F; + bgfx::TextureHandle fsrEasuTexture = m_config.m_fsr16Bit ? m_resources->m_easuTexture16F : m_resources->m_easuTexture32F; + + // EASU pass (upscale) + { + bgfx::ProgramHandle program = m_config.m_fsr16Bit ? m_resources->m_easu16Program : m_resources->m_easu32Program; + + if (!m_config.m_applyFsr) + { + program = m_resources->m_bilinear32Program; + } + + bgfx::setViewName(view, "fsr easu"); + m_resources->m_uniforms.submit(); + bgfx::setTexture(0, m_resources->s_inputTexture, _colorTexture); + bgfx::setImage(1, fsrEasuTexture, 0, bgfx::Access::Write, format); + bgfx::dispatch(view, program, dispatchX, dispatchY, 1); + ++view; + } + + // RCAS pass (sharpening) + if (m_config.m_applyFsrRcas) + { + bgfx::ProgramHandle program = m_config.m_fsr16Bit ? m_resources->m_rcas16Program : m_resources->m_rcas32Program; + + bgfx::setViewName(view, "fsr rcas"); + m_resources->m_uniforms.submit(); + bgfx::setTexture(0, m_resources->s_inputTexture, fsrEasuTexture); + bgfx::setImage(1, m_config.m_fsr16Bit ? m_resources->m_rcasTexture16F : m_resources->m_rcasTexture32F, 0, bgfx::Access::Write, format); + bgfx::dispatch(view, program, dispatchX, dispatchY, 1); + ++view; + } + + return view; +} + +bgfx::TextureHandle Fsr::getResultTexture() const +{ + if (m_config.m_applyFsr && m_config.m_applyFsrRcas) + { + return m_config.m_fsr16Bit ? m_resources->m_rcasTexture16F : m_resources->m_rcasTexture32F; + } + else + { + return m_config.m_fsr16Bit ? m_resources->m_easuTexture16F : m_resources->m_easuTexture32F; + } +} + +bool Fsr::supports16BitPrecision() const +{ + return m_support16BitPrecision; +} + +void Fsr::updateUniforms() +{ + float const srcWidth = static_cast(m_resources->m_width) / m_config.m_superSamplingFactor; + float const srcHeight = static_cast(m_resources->m_height) / m_config.m_superSamplingFactor; + + m_resources->m_uniforms.ViewportSizeRcasAttenuation.x = srcWidth; + m_resources->m_uniforms.ViewportSizeRcasAttenuation.y = srcHeight; + m_resources->m_uniforms.ViewportSizeRcasAttenuation.z = m_config.m_rcasAttenuation; + m_resources->m_uniforms.SrcSize.x = static_cast(m_resources->m_width); + m_resources->m_uniforms.SrcSize.y = static_cast(m_resources->m_height); + m_resources->m_uniforms.DstSize.x = static_cast(m_resources->m_width); + m_resources->m_uniforms.DstSize.y = static_cast(m_resources->m_height); +} diff --git a/examples/46-fsr/fsr.h b/examples/46-fsr/fsr.h new file mode 100644 index 000000000..186e4df45 --- /dev/null +++ b/examples/46-fsr/fsr.h @@ -0,0 +1,47 @@ +/* +* Copyright 2021 Richard Schubert. All rights reserved. +* License: https://github.com/bkaradzic/bgfx#license-bsd-2-clause +* +* AMD FidelityFX Super Resolution 1.0 (FSR) +* Based on https://github.com/GPUOpen-Effects/FidelityFX-FSR/blob/master/sample/ +*/ + +#ifndef __FSR_H__ +#define __FSR_H__ + +#include + +class Fsr +{ +public: + + struct Config + { + bool m_applyFsr{ true }; + bool m_applyFsrRcas{ true }; + float m_superSamplingFactor{ 2.0f }; + float m_rcasAttenuation{ 0.2f }; + bool m_fsr16Bit{ false }; + }; + + Config m_config; + + Fsr(); + ~Fsr(); + + void init(uint32_t _width, uint32_t _height); + void destroy(); + void resize(uint32_t _width, uint32_t _height); + + bgfx::ViewId computeFsr(bgfx::ViewId _pass, bgfx::TextureHandle _colorTexture); + bgfx::TextureHandle getResultTexture() const; + bool supports16BitPrecision() const; + +private: + void updateUniforms(); + + struct FsrResources *m_resources; + bool m_support16BitPrecision{ false }; +}; + +#endif // __FSR_H__ diff --git a/examples/46-fsr/makefile b/examples/46-fsr/makefile new file mode 100644 index 000000000..a88986ef6 --- /dev/null +++ b/examples/46-fsr/makefile @@ -0,0 +1,10 @@ +# +# Copyright 2011-2021 Branimir Karadzic. All rights reserved. +# License: https://github.com/bkaradzic/bgfx#license-bsd-2-clause +# + +BGFX_DIR=../.. +RUNTIME_DIR=$(BGFX_DIR)/examples/runtime +BUILD_DIR=../../.build + +include $(BGFX_DIR)/scripts/shader.mk diff --git a/examples/46-fsr/screenshot.png b/examples/46-fsr/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..901f2b84e08620e2132faf94e3617505216e07b5 GIT binary patch literal 180092 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

SIxIR!F?-S(#! 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

uL#<4UE| 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