* Copyright (c) 2026 Huawei Device Co., Ltd.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "ge_spatial_point_light.h"
#include <algorithm>
#include "ge_log.h"
#include "ge_shader_diagnostics.h"
#include "ge_visual_effect_impl.h"
#undef LOG_TAG
#define LOG_TAG "GESpatialPointLightShader"
namespace OHOS {
namespace Rosen {
namespace {
constexpr int32_t COLOR_CHANNEL = 4;
constexpr int32_t POSITION_DIMENSION = 3;
constexpr float FADE_EDGE_LOW = 0.01f;
constexpr float FADE_EDGE_HIGH = 0.04f;
static constexpr char PROG_NO_MASK[] = R"(
uniform vec2 iResolution;
uniform vec3 lightPosition;
uniform half4 lightColor;
uniform half lightIntensity;
uniform half attenuation;
uniform float fadeEdgeLow;
uniform float fadeEdgeHigh;
half4 main(vec2 fragCoord)
{
vec3 lightDirection = lightPosition - vec3(fragCoord.x, fragCoord.y, 0.0);
vec3 halfwayDir = normalize(lightDirection);
float spec = pow(max(dot(vec3(0.0, 0.0, 1.0), halfwayDir), 0.0), attenuation);
vec4 fragColor = spec * lightIntensity * lightColor;
fragColor *= smoothstep(fadeEdgeLow, fadeEdgeHigh, fragColor);
return fragColor;
}
)";
static constexpr char PROG_WITH_MASK[] = R"(
uniform vec2 iResolution;
uniform vec3 lightPosition;
uniform half4 lightColor;
uniform half lightIntensity;
uniform half attenuation;
uniform float fadeEdgeLow;
uniform float fadeEdgeHigh;
uniform shader mask;
half4 main(vec2 fragCoord)
{
half4 normalMap = mask.eval(fragCoord);
vec3 lightDirection = lightPosition - vec3(fragCoord.x, fragCoord.y, 0.0);
vec3 halfwayDir = normalize(lightDirection);
float spec = pow(max(dot(vec3(0.0, 0.0, 1.0), halfwayDir), 0.0), attenuation);
vec4 fragColor = spec * lightIntensity * lightColor * normalMap.r;
fragColor *= smoothstep(fadeEdgeLow, fadeEdgeHigh, fragColor);
return fragColor;
}
)";
Drawing::RuntimeEffectOptions g_highpUVOption{ .useHighpLocalCoords = true };
}
GESpatialPointLightShader::GESpatialPointLightShader() {}
GESpatialPointLightShader::GESpatialPointLightShader(Drawing::GESpatialPointLightShaderParams& param)
{
pointLightParams_ = param;
GE_LOGD("ctor: intensity=%{public}f, attenuation=%{public}f",
pointLightParams_.lightIntensity, pointLightParams_.attenuation);
}
void GESpatialPointLightShader::MakeDrawingShader(const Drawing::Rect& rect, float progress)
{
drShader_ = MakeSpatialPointLightShader(rect);
}
std::shared_ptr<Drawing::RuntimeShaderBuilder> GESpatialPointLightShader::GetSpatialPointLightBuilderNoMask()
{
thread_local std::shared_ptr<Drawing::RuntimeShaderBuilder> shaderBuilder;
if (shaderBuilder) {
return shaderBuilder;
}
auto effect = GECreateRuntimeEffectForShader(PROG_NO_MASK, g_highpUVOption);
if (effect == nullptr) {
GE_LOGE("GetSpatialPointLightBuilderNoMask effect is nullptr.");
return nullptr;
}
GE_LOGD("RuntimeEffect (no mask) created");
shaderBuilder = std::make_shared<Drawing::RuntimeShaderBuilder>(effect);
return shaderBuilder;
}
std::shared_ptr<Drawing::RuntimeShaderBuilder> GESpatialPointLightShader::GetSpatialPointLightBuilderWithMask()
{
thread_local std::shared_ptr<Drawing::RuntimeShaderBuilder> shaderBuilder;
if (shaderBuilder) {
return shaderBuilder;
}
auto effect = GECreateRuntimeEffectForShader(PROG_WITH_MASK, g_highpUVOption);
if (effect == nullptr) {
GE_LOGE("GetSpatialPointLightBuilderWithMask effect is nullptr.");
return nullptr;
}
GE_LOGD("RuntimeEffect (with mask) created");
shaderBuilder = std::make_shared<Drawing::RuntimeShaderBuilder>(effect);
return shaderBuilder;
}
std::shared_ptr<Drawing::ShaderEffect> GESpatialPointLightShader::MakeSpatialPointLightShader(const Drawing::Rect& rect)
{
auto width = rect.GetWidth();
auto height = rect.GetHeight();
if (width < 1e-6 || height < 1e-6) {
GE_LOGE("MakeSpatialPointLightShader invalid rect size: width=%{public}f, height=%{public}f",
width, height);
return nullptr;
}
std::shared_ptr<Drawing::RuntimeShaderBuilder> builder;
if (pointLightParams_.mask != nullptr) {
builder = GetSpatialPointLightBuilderWithMask();
} else {
builder = GetSpatialPointLightBuilderNoMask();
}
if (builder == nullptr) {
GE_LOGE("MakeSpatialPointLightShader builder is nullptr.");
return nullptr;
}
builder->SetUniform("iResolution", width, height);
builder->SetUniform("lightPosition", pointLightParams_.lightPosition.GetData(), POSITION_DIMENSION);
builder->SetUniform("lightColor", pointLightParams_.lightColor.GetData(), COLOR_CHANNEL);
builder->SetUniform("lightIntensity", pointLightParams_.lightIntensity);
float clampedAttenuation = std::max(0.001f, pointLightParams_.attenuation);
builder->SetUniform("attenuation", clampedAttenuation);
builder->SetUniform("fadeEdgeLow", FADE_EDGE_LOW);
builder->SetUniform("fadeEdgeHigh", FADE_EDGE_HIGH);
if (pointLightParams_.mask != nullptr) {
auto maskShader = pointLightParams_.mask->GenerateDrawingShader(width, height);
if (maskShader == nullptr) {
GE_LOGE("MakeSpatialPointLightShader mask shader is nullptr.");
return nullptr;
}
builder->SetChild("mask", maskShader);
}
auto spatialPointLightShader = builder->MakeShader(nullptr, false);
if (spatialPointLightShader == nullptr) {
GE_LOGE("MakeSpatialPointLightShader shader is nullptr.");
return nullptr;
}
return spatialPointLightShader;
}
}
}