Skip to content

Commit

Permalink
Allow Canvas mode effects to set pixels transparent (ex: shaders only…
Browse files Browse the repository at this point in the history
… applying to part of buffer)

Add Black -> Transparent and Transparent -> Black to "Off" effect used for canvas
Adds more options to address #4552
  • Loading branch information
dkulp committed May 8, 2024
1 parent dd59049 commit 94b3e7c
Show file tree
Hide file tree
Showing 14 changed files with 176 additions and 62 deletions.
6 changes: 3 additions & 3 deletions xLights/GPURenderUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,9 @@ class GPURenderUtils {
return false;
}

static bool BlendLayers(PixelBufferClass *pixelBuffer, int effectPeriod, const std::vector<bool>& validLayers, int saveLayer) {
static bool BlendLayers(PixelBufferClass *pixelBuffer, int effectPeriod, const std::vector<bool>& validLayers, int saveLayer, bool saveToPixels) {
if (INSTANCE) {
return INSTANCE->doBlendLayers(pixelBuffer, effectPeriod, validLayers, saveLayer);
return INSTANCE->doBlendLayers(pixelBuffer, effectPeriod, validLayers, saveLayer, saveToPixels);
}
return false;
}
Expand All @@ -110,7 +110,7 @@ class GPURenderUtils {
virtual bool doBlur(RenderBuffer *buffer, int radius) = 0;
virtual bool doRotoZoom(RenderBuffer *buffer, RotoZoomSettings &settings) = 0;
virtual bool doTransitions(PixelBufferClass *pixelBuffer, int layer, RenderBuffer *prevRB) = 0;
virtual bool doBlendLayers(PixelBufferClass *pixelBuffer, int effectPeriod, const std::vector<bool>& validLayers, int saveLayer) = 0;
virtual bool doBlendLayers(PixelBufferClass *pixelBuffer, int effectPeriod, const std::vector<bool>& validLayers, int saveLayer, bool saveToPixels) = 0;

virtual void setPrioritizeGraphics(bool p) = 0;

Expand Down
19 changes: 12 additions & 7 deletions xLights/PixelBuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1264,9 +1264,9 @@ void PixelBufferClass::mixColors(const wxCoord& x, const wxCoord& y, xlColor& fg
}
case MixTypes::Mix_Average:
// only average when both colors are non-black
if (bg == xlBLACK) {
if (bg == xlBLACK || bg.alpha == 0) {
bg = fg;
} else if (fg != xlBLACK) {
} else if (fg != xlBLACK && fg.alpha != 0) {
bg.Set((fg.Red() + bg.Red()) / 2, (fg.Green() + bg.Green()) / 2, (fg.Blue() + bg.Blue()) / 2, (fg.alpha + bg.alpha) / 2);
}
break;
Expand Down Expand Up @@ -1361,7 +1361,7 @@ void PixelBufferClass::mixColors(const wxCoord& x, const wxCoord& y, xlColor& fg
}
}

void PixelBufferClass::GetMixedColor(int node, const std::vector<bool>& validLayers, int EffectPeriod, int saveLayer) {
void PixelBufferClass::GetMixedColor(int node, const std::vector<bool>& validLayers, int EffectPeriod, int saveLayer, bool saveToPixels) {
int cnt = 0;
xlColor c(xlBLACK);
xlColor color;
Expand Down Expand Up @@ -1532,6 +1532,11 @@ void PixelBufferClass::GetMixedColor(int node, const std::vector<bool>& validLay
}
// set color for physical output
layers[saveLayer]->buffer.Nodes[node]->SetColor(c);
if (saveToPixels) {
for (auto &n : layers[saveLayer]->buffer.Nodes[node]->Coords) {
layers[saveLayer]->buffer.SetPixel(n.bufX, n.bufY, c);
}
}
}

void PixelBufferClass::GetMixedColor(int lx, int ly, xlColor& c, const std::vector<bool>& validLayers, int EffectPeriod) {
Expand Down Expand Up @@ -3033,7 +3038,7 @@ void PixelBufferClass::HandleLayerTransitions(int EffectPeriod, int ii) {
}
}

void PixelBufferClass::CalcOutput(int EffectPeriod, const std::vector<bool>& validLayers, int saveLayer) {
void PixelBufferClass::CalcOutput(int EffectPeriod, const std::vector<bool>& validLayers, int saveLayer, bool saveToPixels ) {

// layer calculation and map to output
size_t NodeCount = layers[0]->buffer.Nodes.size();
Expand Down Expand Up @@ -3063,7 +3068,7 @@ void PixelBufferClass::CalcOutput(int EffectPeriod, const std::vector<bool>& val
}
sparkles = &sparklesVector[0];
}
if (!GPURenderUtils::BlendLayers(this, EffectPeriod, validLayers, saveLayer)) {
if (!GPURenderUtils::BlendLayers(this, EffectPeriod, validLayers, saveLayer, saveToPixels)) {
for (int ii = (numLayers - 1); ii >= 0; --ii) {
if (!validLayers[ii]) {
continue;
Expand Down Expand Up @@ -3102,13 +3107,13 @@ void PixelBufferClass::CalcOutput(int EffectPeriod, const std::vector<bool>& val

std::vector<NodeBaseClassPtr>& Nodes = layers[saveLayer]->buffer.Nodes;
parallel_for(
0, NodeCount, [this, &Nodes, &validLayers, saveLayer, EffectPeriod](int i) {
0, NodeCount, [this, &Nodes, &validLayers, saveLayer, saveToPixels, EffectPeriod](int i) {
if (!Nodes[i]->IsVisible()) {
// unmapped pixel - set to black
Nodes[i]->SetColor(xlBLACK);
} else {
// get blend of two effects
GetMixedColor(i, validLayers, EffectPeriod, saveLayer);
GetMixedColor(i, validLayers, EffectPeriod, saveLayer, saveToPixels);
}
},
blockSize);
Expand Down
5 changes: 3 additions & 2 deletions xLights/PixelBuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

enum class MixTypes {
Mix_Normal, /** Layered with Alpha channel considered **/
Mix_Normal_Rev, /** Layered with Alpha channel considered, bg onto fg **/
Mix_Effect1, /**< Effect 1 only */
Mix_Effect2, /**< Effect 2 only */
Mix_Mask1, /**< Effect 2 color shows where Effect 1 is black */
Expand Down Expand Up @@ -246,7 +247,7 @@ class PixelBufferClass {
void RotateY(RenderBuffer& buffer, GPURenderUtils::RotoZoomSettings& settings);
void RotateZAndZoom(RenderBuffer& buffer, GPURenderUtils::RotoZoomSettings& settings);

void GetMixedColor(int node, const std::vector<bool>& validLayers, int EffectPeriod, int saveLayer);
void GetMixedColor(int node, const std::vector<bool>& validLayers, int EffectPeriod, int saveLayer, bool saveToPixels);

std::string modelName;
std::string lastBufferType;
Expand Down Expand Up @@ -313,7 +314,7 @@ class PixelBufferClass {

void HandleLayerBlurZoom(int EffectPeriod, int layer);
void HandleLayerTransitions(int EffectPeriod, int layer);
void CalcOutput(int EffectPeriod, const std::vector<bool>& validLayers, int saveLayer = 0);
void CalcOutput(int EffectPeriod, const std::vector<bool>& validLayers, int saveLayer = 0, bool saveToPixels = false);
void SetColors(int layer, const unsigned char* fdata);
void GetColors(unsigned char* fdata, const std::vector<bool>& restrictRange);

Expand Down
24 changes: 22 additions & 2 deletions xLights/Render.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -552,8 +552,11 @@ class RenderJob: public Job, public NextRenderer {
Effect* tempEffect = nullptr;
int numLayers = el->GetEffectLayerCount();

std::vector<bool> partOfCanvas;
partOfCanvas.resize(info.validLayers.size());
for (int x = 0; x < info.validLayers.size(); x++) {
info.validLayers[x] = false;
partOfCanvas[x] = false;
}

// To support canvas mix type we must render them bottom to top
Expand Down Expand Up @@ -674,6 +677,8 @@ class RenderJob: public Job, public NextRenderer {
}
if (!found) {
vl[i] = false;
} else {
partOfCanvas[i] = true;
}
}
}
Expand All @@ -688,9 +693,17 @@ class RenderJob: public Job, public NextRenderer {
RenderBuffer& rb = buffer->BufferForLayer(layer, -1);

// I have to calc the output here to apply blend, rotozoom and transitions
buffer->CalcOutput(frame, vl, layer);
buffer->CalcOutput(frame, vl, layer, true);
std::vector<uint8_t> done(rb.GetPixelCount());
rb.CopyNodeColorsToPixels(done);
parallel_for(0, rb.GetNodes().size(), [&](int n) {
for (auto &a : rb.GetNodes()[n]->Coords) {
int x = a.bufX;
int y = a.bufY;
if (x >= 0 && x < rb.BufferWi && y >= 0 && y < rb.BufferHt && y*rb.BufferWi + x < rb.GetPixelCount()) {
done[y*rb.BufferWi+x] = true;
}
}
}, 500);
// now fill in any spaces in the buffer that don't have nodes mapped to them
parallel_for(0, rb.BufferHt, [&rb, &buffer, &done, &vl, frame](int y) {
xlColor c;
Expand Down Expand Up @@ -724,6 +737,13 @@ class RenderJob: public Job, public NextRenderer {
if (effectsToUpdate) {
maybeWaitForFrame(frame);
SetCalOutputStatus(frame, info.submodel, strand, -1);
for (int x = 0; x < partOfCanvas.size(); x++) {
// if the layer was used for a canvas effect, we don't want it
// reblended in
if (partOfCanvas[x]) {
info.validLayers[x] = false;
}
}
if (blend) {
buffer->SetColors(numLayers, &((*seqData)[frame][0]));
info.validLayers[numLayers] = true;
Expand Down
16 changes: 0 additions & 16 deletions xLights/RenderBuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1079,22 +1079,6 @@ void RenderBuffer::SetNodePixel(int nodeNum, const xlColor &color, bool dmx_igno
}
}

void RenderBuffer::CopyNodeColorsToPixels(std::vector<uint8_t> &done) {
parallel_for(0, Nodes.size(), [&](int n) {
xlColor c;
Nodes[n]->GetColor(c);
for (auto &a : Nodes[n]->Coords) {
int x = a.bufX;
int y = a.bufY;
if (x >= 0 && x < BufferWi && y >= 0 && y < BufferHt && y*BufferWi + x < pixelVector.size()) {
pixels[y*BufferWi+x] = c;
done[y*BufferWi+x] = true;
}
}
}, 500);
}


//copy src to dest: -DJ
void RenderBuffer::CopyPixel(int srcx, int srcy, int destx, int desty)
{
Expand Down
2 changes: 1 addition & 1 deletion xLights/RenderBuffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -482,8 +482,8 @@ class /*NCCDLLEXPORT*/ RenderBuffer {
}

int GetNodeCount() const { return Nodes.size();}
const std::vector<NodeBaseClassPtr>& GetNodes() const { return Nodes; }
void SetNodePixel(int nodeNum, const xlColor &color, bool dmx_ignore = false);
void CopyNodeColorsToPixels(std::vector<uint8_t> &done);

void CopyPixel(int srcx, int srcy, int destx, int desty);
void ProcessPixel(int x, int y, const xlColor &color, bool wrap_x = false, bool wrap_y = false);
Expand Down
61 changes: 46 additions & 15 deletions xLights/effects/OffEffect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,12 @@ std::list<std::string> OffEffect::CheckEffectSettings(const SettingsMap& setting
std::list<std::string> res = RenderableEffect::CheckEffectSettings(settings, media, model, eff, renderCache);

// if persistent is on then canvas/off transparent cant be checked
if (settings.Get("B_CHECKBOX_OverlayBkg", "0") == "0")
{
if (settings.Get("B_CHECKBOX_OverlayBkg", "0") == "0") {
if (settings.Get("T_CHECKBOX_Canvas", "0") == "1" &&
settings.Get("E_CHECKBOX_Off_Transparent", "0") == "0")
{
settings.Get("E_CHOICE_Off_Style", "Black") == "Black") {
res.push_back(wxString::Format(" WARN: Canvas mode enabled on a off effect but effect is not transparent. This does nothing and slows down rendering. Effect: Off, Model: %s, Start %s", model->GetFullName(), FORMATTIME(eff->GetStartTimeMS())).ToStdString());
}
else if (settings.Get("T_CHECKBOX_Canvas", "0") == "0" &&
settings.Get("E_CHECKBOX_Off_Transparent", "0") == "1")
{
} else if (settings.Get("T_CHECKBOX_Canvas", "0") == "0" &&
settings.Get("E_CHOICE_Off_Style", "Black") != "Black") {
res.push_back(wxString::Format(" WARN: Canvas mode not enabled on a off effect and effect is transparent. This does not do anything useful. Effect: Off, Model: %s, Start %s", model->GetFullName(), FORMATTIME(eff->GetStartTimeMS())).ToStdString());
}
}
Expand All @@ -56,23 +52,58 @@ xlEffectPanel *OffEffect::CreatePanel(wxWindow *parent) {

void OffEffect::SetDefaultParameters() {
OffPanel *p = (OffPanel*)panel;
p->CheckBox_Transparent->SetValue(false);
p->OffStyleChoice->SetSelection(0);
}

bool OffEffect::needToAdjustSettings(const std::string& version) {
if (IsVersionOlder("2024.11", version)) {
return true;
}
return RenderableEffect::needToAdjustSettings(version);
}
void OffEffect::adjustSettings(const std::string& version, Effect* effect, bool removeDefaults) {
RenderableEffect::adjustSettings(version, effect, removeDefaults);
std::string i = effect->GetSettings().Get("E_CHECKBOX_Off_Transparent", "");
if (i != "") {
effect->GetSettings().erase("E_CHECKBOX_Off_Transparent");
if (i == "1") {
effect->GetSettings()["E_CHOICE_Off_Style"] = "Transparent";
}
}
}


void OffEffect::Render(Effect* effect, const SettingsMap& settings, RenderBuffer& buffer)
{
// dont change any pixels at all if we are transparent
if (settings.GetBool("CHECKBOX_Off_Transparent", false)) return;
std::string style = settings.Get("CHOICE_Off_Style", "Black");
if (style == "Transparent") {
// dont change any pixels at all if we are transparent
return;
} else if (style == "Black") {
// Every Node, every frame set to BLACK
buffer.Fill(xlBLACK);
} else if (style == "Black -> Transparent") {
for (int x = 0; x < buffer.GetPixelCount(); x++) {
if (buffer.GetPixels()[x] == xlBLACK) {
buffer.GetPixels()[x] = xlCLEAR;
}
}
} else if (style == "Transparent -> Black") {
for (int x = 0; x < buffer.GetPixelCount(); x++) {
if (buffer.GetPixels()[x] == xlCLEAR) {
buffer.GetPixels()[x] = xlBLACK;
}
}
}

// Every Node, every frame set to BLACK
buffer.Fill(xlBLACK);
}

wxString OffEffect::GetEffectString()
{
OffPanel* p = (OffPanel*)panel;
if (p->CheckBox_Transparent->GetValue()) {
return "E_CHECKBOX_Off_Transparent=1,";
std::string style = p->OffStyleChoice->GetStringSelection().ToStdString();
if (style != "Black") {
return "E_CHOICE_Off_Style=" + style + ",";
}
return "";
}
3 changes: 3 additions & 0 deletions xLights/effects/OffEffect.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ class OffEffect : public RenderableEffect
virtual bool CanRenderPartialTimeInterval() const override { return true; }
virtual std::list<std::string> CheckEffectSettings(const SettingsMap& settings, AudioManager* media, Model* model, Effect* eff, bool renderCache) override;

virtual bool needToAdjustSettings(const std::string& version) override;
virtual void adjustSettings(const std::string& version, Effect* effect, bool removeDefaults = true) override;

protected:
virtual xlEffectPanel *CreatePanel(wxWindow *parent) override;
private:
Expand Down
19 changes: 14 additions & 5 deletions xLights/effects/OffPanel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
#include "OffPanel.h"

//(*InternalHeaders(OffPanel)
#include <wx/checkbox.h>
#include <wx/choice.h>
#include <wx/intl.h>
#include <wx/sizer.h>
#include <wx/stattext.h>
Expand All @@ -20,7 +20,7 @@

//(*IdInit(OffPanel)
const long OffPanel::ID_STATICTEXT_Off_Text = wxNewId();
const long OffPanel::ID_CHECKBOX_Off_Transparent = wxNewId();
const long OffPanel::ID_CHOICE_Off_Style = wxNewId();
//*)

BEGIN_EVENT_TABLE(OffPanel,wxPanel)
Expand All @@ -31,17 +31,26 @@ END_EVENT_TABLE()
OffPanel::OffPanel(wxWindow* parent) : xlEffectPanel(parent)
{
//(*Initialize(OffPanel)
wxFlexGridSizer* FlexGridSizer1;
wxFlexGridSizer* FlexGridSizer77;
wxStaticText* StaticText1;
wxStaticText* StaticText2;

Create(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL, _T("wxID_ANY"));
FlexGridSizer77 = new wxFlexGridSizer(0, 1, 0, 0);
FlexGridSizer77->AddGrowableCol(0);
StaticText1 = new wxStaticText(this, ID_STATICTEXT_Off_Text, _("This Effect simply turns every pixel off on this model.\n\nIf set to be transparent it does not change any pixels."), wxDefaultPosition, wxDefaultSize, 0, _T("ID_STATICTEXT_Off_Text"));
FlexGridSizer77->Add(StaticText1, 1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5);
CheckBox_Transparent = new BulkEditCheckBox(this, ID_CHECKBOX_Off_Transparent, _("Transparent"), wxDefaultPosition, wxDefaultSize, 0, wxDefaultValidator, _T("ID_CHECKBOX_Off_Transparent"));
CheckBox_Transparent->SetValue(false);
FlexGridSizer77->Add(CheckBox_Transparent, 1, wxALL|wxEXPAND, 5);
FlexGridSizer1 = new wxFlexGridSizer(0, 3, 0, 0);
StaticText2 = new wxStaticText(this, wxID_ANY, _("Style:"), wxDefaultPosition, wxDefaultSize, 0, _T("wxID_ANY"));
FlexGridSizer1->Add(StaticText2, 1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5);
OffStyleChoice = new wxChoice(this, ID_CHOICE_Off_Style, wxDefaultPosition, wxDefaultSize, 0, 0, 0, wxDefaultValidator, _T("ID_CHOICE_Off_Style"));
OffStyleChoice->SetSelection( OffStyleChoice->Append(_("Black")) );
OffStyleChoice->Append(_("Transparent"));
OffStyleChoice->Append(_("Black -> Transparent"));
OffStyleChoice->Append(_("Transparent -> Black"));
FlexGridSizer1->Add(OffStyleChoice, 1, wxALL|wxALIGN_CENTER_HORIZONTAL|wxALIGN_CENTER_VERTICAL, 5);
FlexGridSizer77->Add(FlexGridSizer1, 1, wxALL|wxEXPAND, 0);
SetSizer(FlexGridSizer77);
//*)

Expand Down
6 changes: 3 additions & 3 deletions xLights/effects/OffPanel.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

//(*Headers(OffPanel)
#include <wx/panel.h>
class wxCheckBox;
class wxChoice;
class wxFlexGridSizer;
class wxStaticText;
//*)
Expand All @@ -29,14 +29,14 @@ class OffPanel: public xlEffectPanel
virtual void ValidateWindow() override;

//(*Declarations(OffPanel)
BulkEditCheckBox* CheckBox_Transparent;
wxChoice* OffStyleChoice;
//*)

protected:

//(*Identifiers(OffPanel)
static const long ID_STATICTEXT_Off_Text;
static const long ID_CHECKBOX_Off_Transparent;
static const long ID_CHOICE_Off_Style;
//*)

private:
Expand Down
2 changes: 1 addition & 1 deletion xLights/effects/metal/MetalComputeUtilities.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class MetalPixelBufferComputeData {
~MetalPixelBufferComputeData();

bool doTransitions(PixelBufferClass *pixelBuffer, int layer, RenderBuffer *prevRB);
bool doBlendLayers(PixelBufferClass *pixelBuffer, int effectPeriod, const std::vector<bool>& validLayers, int saveLayer);
bool doBlendLayers(PixelBufferClass *pixelBuffer, int effectPeriod, const std::vector<bool>& validLayers, int saveLayer, bool saveToPixels);

bool doTransition(id<MTLComputePipelineState> &f, TransitionData &data, RenderBuffer *buffer, RenderBuffer *prevRB);
bool doMap(id<MTLComputePipelineState> &f, TransitionData &data, RenderBuffer *buffer);
Expand Down

0 comments on commit 94b3e7c

Please sign in to comment.